go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tq/internal/testutil/dbtest.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package testutil
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  	"time"
    21  
    22  	"go.chromium.org/luci/server/tq/internal/db"
    23  	"go.chromium.org/luci/server/tq/internal/partition"
    24  	"go.chromium.org/luci/server/tq/internal/reminder"
    25  
    26  	. "github.com/smartystreets/goconvey/convey"
    27  )
    28  
    29  // RunDBAcceptance tests a database implementation.
    30  //
    31  // Sadly, GoConvey-reported error lines are borked because it doesn't search
    32  // stack in files other than "*_test.go" and "*_tests.go", which this file
    33  // can't be as it is in a different package.
    34  // However, you can run tests for your database via $ go test
    35  // and examine stacktraces for actual true line nimber.
    36  // Or, you can copy this file to your package during debugging.
    37  func RunDBAcceptance(ctx context.Context, db db.DB, t *testing.T) {
    38  	t.Parallel()
    39  
    40  	epoch := time.Date(2020, time.February, 3, 4, 5, 6, 0, time.UTC)
    41  
    42  	// Test uses keySpaceBytes=1, meaning [0..256) keyspace.
    43  	mkReminder := func(id int64, freshUntil time.Time, payload string) *reminder.Reminder {
    44  		var payloadBytes []byte
    45  		if payload != "" { // prefer nil instead of []byte{}
    46  			payloadBytes = []byte(payload)
    47  		}
    48  		low, _ := partition.FromInts(id, id+1).QueryBounds(1)
    49  		return &reminder.Reminder{
    50  			ID:         low,
    51  			FreshUntil: freshUntil,
    52  			RawPayload: payloadBytes,
    53  		}
    54  	}
    55  
    56  	Convey("Test DB "+db.Kind(), t, func() {
    57  		Convey("Save & Delete", func() {
    58  			r := mkReminder(1, epoch, "payload")
    59  			So(db.SaveReminder(ctx, r), ShouldBeNil)
    60  			So(db.DeleteReminder(ctx, r), ShouldBeNil)
    61  			Convey("Delete non-existing is a noop", func() {
    62  				So(db.DeleteReminder(ctx, r), ShouldBeNil)
    63  			})
    64  		})
    65  
    66  		Convey("FetchRemindersMeta", func() {
    67  			So(db.SaveReminder(ctx, mkReminder(254, epoch.Add(time.Second), "254")), ShouldBeNil)
    68  			So(db.SaveReminder(ctx, mkReminder(100, epoch.Add(time.Minute), "pay")), ShouldBeNil)
    69  			So(db.SaveReminder(ctx, mkReminder(255, epoch.Add(time.Hour), "load")), ShouldBeNil)
    70  
    71  			Convey("All + sorted", func() {
    72  				res, err := db.FetchRemindersMeta(ctx, "00", "g", 5)
    73  				So(err, ShouldBeNil)
    74  				So(res, ShouldResemble, []*reminder.Reminder{
    75  					mkReminder(100, epoch.Add(time.Minute), ""),
    76  					mkReminder(254, epoch.Add(time.Second), ""),
    77  					mkReminder(255, epoch.Add(time.Hour), ""),
    78  				})
    79  			})
    80  
    81  			Convey("Limit", func() {
    82  				res, err := db.FetchRemindersMeta(ctx, "00", "g", 2)
    83  				So(err, ShouldBeNil)
    84  				So(res, ShouldResemble, []*reminder.Reminder{
    85  					mkReminder(100, epoch.Add(time.Minute), ""),
    86  					mkReminder(254, epoch.Add(time.Second), ""),
    87  				})
    88  			})
    89  
    90  			Convey("Obey partition", func() {
    91  				res, err := db.FetchRemindersMeta(ctx, "00", "ee", 5)
    92  				So(err, ShouldBeNil)
    93  				So(res, ShouldResemble, []*reminder.Reminder{
    94  					mkReminder(100, epoch.Add(time.Minute), ""),
    95  				})
    96  			})
    97  		})
    98  
    99  		Convey("FetchReminderRawPayloads", func() {
   100  			all := []*reminder.Reminder{
   101  				mkReminder(100, epoch.Add(time.Minute), "pay"),
   102  				mkReminder(254, epoch.Add(time.Second), "254"),
   103  				mkReminder(255, epoch.Add(time.Hour), "load"),
   104  			}
   105  			meta := make([]*reminder.Reminder, len(all))
   106  			for i, r := range all {
   107  				meta[i] = &reminder.Reminder{ID: r.ID, FreshUntil: r.FreshUntil}
   108  				So(db.SaveReminder(ctx, r), ShouldBeNil)
   109  			}
   110  
   111  			Convey("All", func() {
   112  				res, err := db.FetchReminderRawPayloads(ctx, meta)
   113  				So(err, ShouldBeNil)
   114  				So(res, ShouldResemble, all)
   115  				Convey("Re-use objects", func() {
   116  					So(meta, ShouldResemble, all)
   117  				})
   118  			})
   119  
   120  			Convey("Some", func() {
   121  				second := &reminder.Reminder{ID: meta[1].ID, FreshUntil: meta[1].FreshUntil}
   122  				So(db.DeleteReminder(ctx, second), ShouldBeNil)
   123  				res, err := db.FetchReminderRawPayloads(ctx, meta)
   124  				So(err, ShouldBeNil)
   125  				So(res, ShouldResemble, []*reminder.Reminder{all[0], all[2]})
   126  				Convey("Re-use objects", func() {
   127  					So(meta[0], ShouldResemble, all[0])
   128  					So(meta[2], ShouldResemble, all[2])
   129  				})
   130  			})
   131  		})
   132  	})
   133  }