func Test(t *testing.T) { t.Parallel() Convey("test mathrand", t, func() { now := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) c, _ := testclock.UseTime(context.Background(), now) // Note that the non-randomness below is because time is fixed at the // top of the outer test function. Normally it would evolve with time. Convey("unset", func() { r := rand.New(rand.NewSource(now.UnixNano())) i := r.Int() So(Get(c).Int(), ShouldEqual, i) So(Get(c).Int(), ShouldEqual, i) }) Convey("set persistance", func() { c = Set(c, rand.New(rand.NewSource(now.UnixNano()))) r := rand.New(rand.NewSource(now.UnixNano())) So(Get(c).Int(), ShouldEqual, r.Int()) So(Get(c).Int(), ShouldEqual, r.Int()) }) Convey("nil set", func() { c = Set(c, nil) r := rand.New(rand.NewSource(now.UnixNano())) i := r.Int() So(Get(c).Int(), ShouldEqual, i) So(Get(c).Int(), ShouldEqual, i) }) }) }
func TestCloudLogging(t *testing.T) { Convey(`A cloud logging instance using a test client`, t, func() { now := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) ctx, _ := testclock.UseTime(context.Background(), now) client := &testClient{ logC: make(chan []*cloudlogging.Entry, 1), } config := Config{ InsertIDBase: "totally-random", } ctx = Use(ctx, config, client) Convey(`Can publish logging data.`, func() { logging.Fields{ "foo": "bar", }.Infof(ctx, "Message at %s", "INFO") entries := <-client.logC So(len(entries), ShouldEqual, 1) So(entries[0], ShouldResemble, &cloudlogging.Entry{ InsertID: "totally-random.0.0", Timestamp: now, Severity: cloudlogging.Info, Labels: cloudlogging.Labels{ "foo": "bar", }, TextPayload: `Message at INFO {"foo":"bar"}`, }) }) }) }
func TestLimited(t *testing.T) { Convey(`A Limited Iterator, using an instrumented context`, t, func() { ctx, clock := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) l := Limited{} Convey(`When empty, will return Stop immediately..`, func() { So(l.Next(ctx, nil), ShouldEqual, Stop) }) Convey(`With 3 retries, will Stop after three retries.`, func() { l.Delay = time.Second l.Retries = 3 So(l.Next(ctx, nil), ShouldEqual, time.Second) So(l.Next(ctx, nil), ShouldEqual, time.Second) So(l.Next(ctx, nil), ShouldEqual, time.Second) So(l.Next(ctx, nil), ShouldEqual, Stop) }) Convey(`Will stop after MaxTotal.`, func() { l.Retries = 1000 l.Delay = 3 * time.Second l.MaxTotal = 8 * time.Second So(l.Next(ctx, nil), ShouldEqual, 3*time.Second) clock.Add(8 * time.Second) So(l.Next(ctx, nil), ShouldEqual, Stop) }) }) }
// Run for 5s, every 1s. Should run 5 times before returning Success. func TestLoopMultiple(t *testing.T) { ctx := context.Background() _, c := testclock.UseTime(context.Background(), time.Unix(0, 0)) nCalls := 0 called := make(chan interface{}, 1) f := func(ctx context.Context) error { nCalls++ called <- nil return nil } ctx, cancel := context.WithCancel(ctx) done := make(chan struct{}) go func() { res := Run(ctx, f, 1*time.Second, 1, c) if !res.Success { t.Errorf("Should have succeeded.") } if nCalls != 5 { t.Errorf("Want %d got %d", 5, nCalls) } close(done) }() // Read off the rest of the timer ticks. for i := 0; i < 5; i++ { c.Add(1 * time.Second) <-called } cancel() <-done }
func TestExponentialBackoff(t *testing.T) { Convey(`An ExponentialBackoff Iterator, using an instrumented context`, t, func() { ctx, _ := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) l := ExponentialBackoff{} Convey(`When empty, will Stop immediatley.`, func() { So(l.Next(ctx, nil), ShouldEqual, Stop) }) Convey(`Will delay exponentially.`, func() { l.Retries = 4 l.Delay = time.Second So(l.Next(ctx, nil), ShouldEqual, 1*time.Second) So(l.Next(ctx, nil), ShouldEqual, 2*time.Second) So(l.Next(ctx, nil), ShouldEqual, 4*time.Second) So(l.Next(ctx, nil), ShouldEqual, 8*time.Second) So(l.Next(ctx, nil), ShouldEqual, Stop) }) Convey(`Will bound exponential delay when MaxDelay is set.`, func() { l.Retries = 4 l.Delay = time.Second l.MaxDelay = 4 * time.Second So(l.Next(ctx, nil), ShouldEqual, 1*time.Second) So(l.Next(ctx, nil), ShouldEqual, 2*time.Second) So(l.Next(ctx, nil), ShouldEqual, 4*time.Second) So(l.Next(ctx, nil), ShouldEqual, 4*time.Second) So(l.Next(ctx, nil), ShouldEqual, Stop) }) }) }
// Run f once, then cancel. func TestLoopOnce(t *testing.T) { ctx := context.Background() _, c := testclock.UseTime(context.Background(), time.Unix(0, 0)) nCalls := 0 called := make(chan interface{}, 1) f := func(ctx context.Context) error { nCalls++ called <- nil return nil } ctx, cancel := context.WithCancel(ctx) done := make(chan struct{}) go func() { res := Run(ctx, f, 1*time.Second, 1, c) if !res.Success { t.Errorf("Should have succeeded.") } if nCalls != 1 { t.Errorf("Want %d got %d", 1, nCalls) } close(done) }() <-called cancel() <-done }
// Run for 10s, every 1s. Every other task takes 1.5s to run. 3 + 3*1.5? func TestLoopOverrunSome(t *testing.T) { ctx := context.Background() _, c := testclock.UseTime(context.Background(), time.Unix(0, 0)) nCalls := 0 called := make(chan interface{}, 1) f := func(ctx context.Context) error { if nCalls%2 == 0 { c.Add(1500 * time.Millisecond) } else { c.Add(500 * time.Millisecond) } nCalls++ called <- nil return nil } ctx, cancel := context.WithCancel(ctx) done := make(chan struct{}) go func() { res := Run(ctx, f, 1*time.Second, 3, c) if !res.Success { t.Errorf("Should have succeeded.") } if nCalls != 10 { t.Errorf("Want %d calls got %d", 10, nCalls) } if res.Errs != 0 { t.Errorf("Want %d Errs got %d", 0, res.Errs) } if res.Overruns != 5 { t.Errorf("Want %d Overruns got %d", 5, res.Overruns) } close(done) }() for i := 0; i < 10; i++ { c.Add(1 * time.Second) <-called } cancel() <-done }
// Run for 10s, every 1s. Return errors on 2nd, 4th, 6th, 7th and 8th calls. func TestLoopMaxErrors(t *testing.T) { ctx := context.Background() _, c := testclock.UseTime(context.Background(), time.Unix(0, 0)) nCalls := 0 called := make(chan interface{}, 1) f := func(ctx context.Context) error { nCalls++ called <- nil if nCalls%2 == 0 || nCalls > 5 { return fmt.Errorf("this is an error: %d", nCalls) } return nil } ctx, cancel := context.WithCancel(ctx) done := make(chan struct{}) go func() { res := Run(ctx, f, 1*time.Second, 3, c) if res.Success { t.Errorf("Should have failed.") } if nCalls != 8 { t.Errorf("Want %d calls got %d", 6, nCalls) } if res.Errs != 5 { t.Errorf("Want %d Errs got %d", 4, res.Errs) } close(done) }() for i := 0; i < 8; i++ { c.Add(1 * time.Second) <-called } cancel() <-done }
func TestTaskQueue(t *testing.T) { t.Parallel() Convey("TaskQueue", t, func() { now := time.Date(2000, time.January, 1, 1, 1, 1, 1, time.UTC) c, tc := testclock.UseTime(context.Background(), now) c = mathrand.Set(c, rand.New(rand.NewSource(clock.Now(c).UnixNano()))) c = Use(c) tq := tqS.Get(c) tqt := tq.Testable() So(tqt, ShouldNotBeNil) So(tq, ShouldNotBeNil) Convey("implements TQMultiReadWriter", func() { Convey("Add", func() { t := tq.NewTask("/hello/world") Convey("works", func() { t.Delay = 4 * time.Second t.Header = http.Header{} t.Header.Add("Cat", "tabby") t.Payload = []byte("watwatwat") t.RetryOptions = &tqS.RetryOptions{AgeLimit: 7 * time.Second} So(tq.Add(t, ""), ShouldBeNil) name := "Z_UjshxM9ecyMQfGbZmUGOEcgxWU0_5CGLl_-RntudwAw2DqQ5-58bzJiWQN4OKzeuUb9O4JrPkUw2rOvk2Ax46THojnQ6avBQgZdrKcJmrwQ6o4qKfJdiyUbGXvy691yRfzLeQhs6cBhWrgf3wH-VPMcA4SC-zlbJ2U8An7I0zJQA5nBFnMNoMgT-2peGoay3rCSbj4z9VFFm9kS_i6JCaQH518ujLDSNCYdjTq6B6lcWrZAh0U_q3a1S2nXEwrKiw_t9MTNQFgAQZWyGBbvZQPmeRYtu8SPaWzTfd25v_YWgBuVL2rRSPSMvlDwE04nNdtvVzE8vNNiA1zRimmdzKeqATQF9_ReUvj4D7U8dcS703DZWfKMBLgBffY9jqCassOOOw77V72Oq5EVauUw3Qw0L6bBsfM9FtahTKUdabzRZjXUoze3EK4KXPt3-wdidau-8JrVf2XFocjjZbwHoxcGvbtT3b4nGLDlgwdC00bwaFBZWff" So(tqt.GetScheduledTasks()["default"][name], ShouldResemble, &tqS.Task{ ETA: now.Add(4 * time.Second), Header: http.Header{"Cat": []string{"tabby"}}, Method: "POST", Name: name, Path: "/hello/world", Payload: []byte("watwatwat"), RetryOptions: &tqS.RetryOptions{AgeLimit: 7 * time.Second}, }) }) Convey("picks up namespace", func() { c, err := info.Get(c).Namespace("coolNamespace") So(err, ShouldBeNil) tq = tqS.Get(c) t := tq.NewTask("") So(tq.Add(t, ""), ShouldBeNil) So(t.Header, ShouldResemble, http.Header{ "X-Appengine-Current-Namespace": {"coolNamespace"}, }) }) Convey("cannot add to bad queues", func() { So(tq.Add(nil, "waaat").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") Convey("but you can add Queues when testing", func() { tqt.CreateQueue("waaat") So(tq.Add(t, "waaat"), ShouldBeNil) Convey("you just can't add them twice", func() { So(func() { tqt.CreateQueue("waaat") }, ShouldPanic) }) }) }) Convey("supplies a URL if it's missing", func() { t.Path = "" So(tq.Add(t, ""), ShouldBeNil) So(t.Path, ShouldEqual, "/_ah/queue/default") }) Convey("cannot add twice", func() { t.Name = "bob" So(tq.Add(t, ""), ShouldBeNil) // can't add the same one twice! So(tq.Add(t, ""), ShouldEqual, tqS.ErrTaskAlreadyAdded) }) Convey("cannot add deleted task", func() { t.Name = "bob" So(tq.Add(t, ""), ShouldBeNil) So(tq.Delete(t, ""), ShouldBeNil) // can't add a deleted task! So(tq.Add(t, ""), ShouldEqual, tqS.ErrTaskAlreadyAdded) }) Convey("cannot set ETA+Delay", func() { t.ETA = clock.Now(c).Add(time.Hour) tc.Add(time.Second) t.Delay = time.Hour So(func() { So(tq.Add(t, ""), ShouldBeNil) }, ShouldPanic) }) Convey("must use a reasonable method", func() { t.Method = "Crystal" So(tq.Add(t, "").Error(), ShouldContainSubstring, "bad method") }) Convey("payload gets dumped for non POST/PUT methods", func() { t.Method = "HEAD" t.Payload = []byte("coool") So(tq.Add(t, ""), ShouldBeNil) So(t.Payload, ShouldBeNil) }) Convey("invalid names are rejected", func() { t.Name = "happy times" So(tq.Add(t, "").Error(), ShouldContainSubstring, "INVALID_TASK_NAME") }) Convey("AddMulti also works", func() { t2 := t.Duplicate() t2.Path = "/hi/city" expect := []*tqS.Task{t, t2} So(tq.AddMulti(expect, "default"), ShouldBeNil) So(len(expect), ShouldEqual, 2) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 2) for i := range expect { Convey(fmt.Sprintf("task %d: %s", i, expect[i].Path), func() { So(expect[i].Method, ShouldEqual, "POST") So(expect[i].ETA, ShouldHappenOnOrBefore, now) So(len(expect[i].Name), ShouldEqual, 500) }) } Convey("stats work too", func() { delay := -time.Second * 400 t := tq.NewTask("/somewhere") t.Delay = delay So(tq.Add(t, ""), ShouldBeNil) stats, err := tq.Stats("") So(err, ShouldBeNil) So(stats[0].Tasks, ShouldEqual, 3) So(stats[0].OldestETA, ShouldHappenOnOrBefore, clock.Now(c).Add(delay)) _, err = tq.Stats("noexist") So(err.Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") }) Convey("can purge all tasks", func() { So(tq.Add(&tqS.Task{Path: "/wut/nerbs"}, ""), ShouldBeNil) So(tq.Purge(""), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) Convey("purging a queue which DNE fails", func() { So(tq.Purge("noexist").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") }) }) }) }) Convey("Delete", func() { t := &tqS.Task{Path: "/hello/world"} So(tq.Add(t, ""), ShouldBeNil) Convey("works", func() { err := tq.Delete(t, "") So(err, ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 1) So(tqt.GetTombstonedTasks()["default"][t.Name], ShouldResemble, t) }) Convey("cannot delete a task twice", func() { So(tq.Delete(t, ""), ShouldBeNil) So(tq.Delete(t, "").Error(), ShouldContainSubstring, "TOMBSTONED_TASK") Convey("but you can if you do a reset", func() { tqt.ResetTasks() So(tq.Add(t, ""), ShouldBeNil) So(tq.Delete(t, ""), ShouldBeNil) }) }) Convey("cannot delete from bogus queues", func() { err := tq.Delete(t, "wat") So(err.Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") }) Convey("cannot delete a missing task", func() { t.Name = "tarntioarenstyw" err := tq.Delete(t, "") So(err.Error(), ShouldContainSubstring, "UNKNOWN_TASK") }) Convey("DeleteMulti also works", func() { t2 := t.Duplicate() t2.Name = "" t2.Path = "/hi/city" So(tq.Add(t2, ""), ShouldBeNil) Convey("usually works", func() { So(tq.DeleteMulti([]*tqS.Task{t, t2}, ""), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 2) }) }) }) }) Convey("works with transactions", func() { t := &tqS.Task{Path: "/hello/world"} So(tq.Add(t, ""), ShouldBeNil) t2 := &tqS.Task{Path: "/hi/city"} So(tq.Add(t2, ""), ShouldBeNil) So(tq.Delete(t2, ""), ShouldBeNil) Convey("can view regular tasks", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { tqt := tqS.GetRaw(c).Testable() So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) return nil }, nil), ShouldBeNil) }) Convey("can add a new task", func() { t3 := &tqS.Task{Path: "/sandwitch/victory"} err := dsS.Get(c).RunInTransaction(func(c context.Context) error { tq := tqS.Get(c) tqt := tq.Testable() So(tq.Add(t3, ""), ShouldBeNil) So(t3.Name, ShouldEqual, "") So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"][0], ShouldResemble, t3) return nil }, nil) So(err, ShouldBeNil) for _, tsk := range tqt.GetScheduledTasks()["default"] { if tsk.Name == t.Name { So(tsk, ShouldResemble, t) } else { tsk.Name = "" So(tsk, ShouldResemble, t3) } } So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) }) Convey("can add a new task (but reset the state in a test)", func() { t3 := &tqS.Task{Path: "/sandwitch/victory"} ttq := tqS.Interface(nil) So(dsS.Get(c).RunInTransaction(func(c context.Context) error { ttq = tqS.Get(c) tqt := ttq.Testable() So(ttq.Add(t3, ""), ShouldBeNil) So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"][0], ShouldResemble, t3) tqt.ResetTasks() So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) return nil }, nil), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTombstonedTasks()["default"]), ShouldEqual, 0) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) Convey("and reusing a closed context is bad times", func() { So(ttq.Add(nil, "").Error(), ShouldContainSubstring, "expired") }) }) Convey("you can AddMulti as well", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { tq := tqS.Get(c) tqt := tq.Testable() t.Name = "" tasks := []*tqS.Task{t.Duplicate(), t.Duplicate(), t.Duplicate()} So(tq.AddMulti(tasks, ""), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 1) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 3) return nil }, nil), ShouldBeNil) So(len(tqt.GetScheduledTasks()["default"]), ShouldEqual, 4) So(len(tqt.GetTransactionTasks()["default"]), ShouldEqual, 0) }) Convey("unless you add too many things", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { for i := 0; i < 5; i++ { So(tqS.Get(c).Add(t.Duplicate(), ""), ShouldBeNil) } So(tqS.Get(c).Add(t, "").Error(), ShouldContainSubstring, "BAD_REQUEST") return nil }, nil), ShouldBeNil) }) Convey("unless you Add to a bad queue", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { So(tqS.Get(c).Add(t, "meat").Error(), ShouldContainSubstring, "UNKNOWN_QUEUE") Convey("unless you add it!", func() { tqS.GetRaw(c).Testable().CreateQueue("meat") So(tqS.Get(c).Add(t, "meat"), ShouldBeNil) }) return nil }, nil), ShouldBeNil) }) Convey("No other features are available, however", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { So(tqS.Get(c).Delete(t, "").Error(), ShouldContainSubstring, "cannot DeleteMulti from a transaction") So(tqS.Get(c).Purge("").Error(), ShouldContainSubstring, "cannot Purge from a transaction") _, err := tqS.Get(c).Stats("") So(err.Error(), ShouldContainSubstring, "cannot Stats from a transaction") return nil }, nil), ShouldBeNil) }) Convey("can get the non-transactional taskqueue context though", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { So(tqS.GetNoTxn(c).Delete(t, ""), ShouldBeNil) So(tqS.GetNoTxn(c).Purge(""), ShouldBeNil) _, err := tqS.GetNoTxn(c).Stats("") So(err, ShouldBeNil) return nil }, nil), ShouldBeNil) }) Convey("adding a new task only happens if we don't errout", func() { So(dsS.Get(c).RunInTransaction(func(c context.Context) error { t3 := tq.NewTask("/sandwitch/victory") So(tqS.Get(c).Add(t3, ""), ShouldBeNil) return fmt.Errorf("nooooo") }, nil), ShouldErrLike, "nooooo") So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) }) Convey("likewise, a panic doesn't schedule anything", func() { func() { defer func() { _ = recover() }() So(dsS.Get(c).RunInTransaction(func(c context.Context) error { tq := tqS.Get(c) So(tq.Add(tq.NewTask("/sandwitch/victory"), ""), ShouldBeNil) panic(fmt.Errorf("nooooo")) }, nil), ShouldBeNil) }() So(tqt.GetScheduledTasks()["default"][t.Name], ShouldResemble, t) So(tqt.GetTombstonedTasks()["default"][t2.Name], ShouldResemble, t2) So(tqt.GetTransactionTasks()["default"], ShouldBeNil) }) }) }) }
func TestCloudLogging(t *testing.T) { Convey(`A cloud logging instance using a test HTTP client/server`, t, func() { ctx, _ := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) // Do not retry. ctx = retry.Use(ctx, func(context.Context) retry.Iterator { return &retry.Limited{} }) h := &testCloudLoggingHandler{ logC: make(chan *cloudLogBundle, 1), } srv := httptest.NewServer(h) defer srv.Close() tr := &http.Transport{ DialTLS: func(network, addr string) (net.Conn, error) { u, err := url.Parse(srv.URL) if err != nil { return nil, err } return net.Dial(network, u.Host) }, } client := &http.Client{ Transport: tr, } config := loggerConfig{ projectName: "test-project", resourceType: "test-resource", logsID: "test-logs-id", } service, err := cloudlog.New(client) So(err, ShouldBeNil) cl := newCloudLogging(ctx, &config, service) defer cl.finish() Convey(`A bound cloud logging instance`, func() { l := cl.bind(ctx) Convey(`Can publish logging data.`, func() { l.Infof("Message at %s", "INFO") bundle := <-h.logC So(len(bundle.Entries), ShouldEqual, 1) So(bundle.Entries[0], shouldMatchLog, &cloudlog.LogEntry{ InsertId: "-0-0", Metadata: &cloudlog.LogEntryMetadata{ ProjectId: "test-project", Severity: "INFO", Timestamp: "2015-01-01T00:00:00Z", }, TextPayload: "Message at INFO", }) }) Convey(`Will batch logging data.`, func() { cl.testLogAckC = make(chan []*logEntry, 1) // The first message will be read immediately. l.Infof("Initial unbatched message.") <-cl.testLogAckC // The next set of messages will be batched, since we're not release our // HTTP server yet. for i := 0; i < cloudLoggingBatchSize; i++ { l.Infof("Batch message #%d", i) } <-cl.testLogAckC // Read the first bundle. bundle := <-h.logC So(len(bundle.Entries), ShouldEqual, 1) So(bundle.Entries[0], shouldMatchLog, &cloudlog.LogEntry{ InsertId: "-0-0", Metadata: &cloudlog.LogEntryMetadata{ ProjectId: "test-project", Severity: "INFO", Timestamp: "2015-01-01T00:00:00Z", }, TextPayload: "Initial unbatched message.", }) // Read the second bundle. bundle = <-h.logC So(len(bundle.Entries), ShouldEqual, cloudLoggingBatchSize) for i, entry := range bundle.Entries { So(entry, shouldMatchLog, &cloudlog.LogEntry{ InsertId: fmt.Sprintf("-1-%d", i), Metadata: &cloudlog.LogEntryMetadata{ ProjectId: "test-project", Severity: "INFO", Timestamp: "2015-01-01T00:00:00Z", }, TextPayload: fmt.Sprintf("Batch message #%d", i), }) } }) }) }) }
func TestSimple(t *testing.T) { // TODO(riannucci): Mock time.After so that we don't have to delay for real. const key = memlockKeyPrefix + "testkey" Convey("basic locking", t, func() { start := time.Date(1986, time.October, 26, 1, 20, 00, 00, time.UTC) ctx, clk := testclock.UseTime(context.Background(), start) blocker := make(chan struct{}) clk.SetTimerCallback(func(clock.Timer) { clk.Add(delay) select { case blocker <- struct{}{}: default: } }) waitFalse := func(ctx context.Context) { loop: for { select { case <-blocker: continue case <-ctx.Done(): break loop } } } ctx, fb := featureBreaker.FilterMC(memory.Use(ctx), nil) mc := memcache.Get(ctx) Convey("fails to acquire when memcache is down", func() { fb.BreakFeatures(nil, "Add") err := TryWithLock(ctx, "testkey", "id", func(context.Context) error { // should never reach here So(false, ShouldBeTrue) return nil }) So(err, ShouldEqual, ErrFailedToLock) }) Convey("returns the inner error", func() { toRet := fmt.Errorf("sup") err := TryWithLock(ctx, "testkey", "id", func(context.Context) error { return toRet }) So(err, ShouldEqual, toRet) }) Convey("returns the error", func() { toRet := fmt.Errorf("sup") err := TryWithLock(ctx, "testkey", "id", func(context.Context) error { return toRet }) So(err, ShouldEqual, toRet) }) Convey("can acquire when empty", func() { err := TryWithLock(ctx, "testkey", "id", func(ctx context.Context) error { isDone := func() bool { select { case <-ctx.Done(): return true default: return false } } So(isDone(), ShouldBeFalse) Convey("waiting for a while keeps refreshing the lock", func() { // simulate waiting for 64*delay time, and ensuring that checkLoop // runs that many times. for i := 0; i < 64; i++ { <-blocker clk.Add(delay) } So(isDone(), ShouldBeFalse) }) Convey("but sometimes we might lose it", func() { Convey("because it was evicted", func() { mc.Delete(key) clk.Add(memcacheLockTime) waitFalse(ctx) }) Convey("or because of service issues", func() { fb.BreakFeatures(nil, "CompareAndSwap") waitFalse(ctx) }) }) return nil }) So(err, ShouldBeNil) }) Convey("can lose it when it gets stolen", func() { gbf := &getBlockerFilter{} ctx = memcache.AddFilters(ctx, func(_ context.Context, mc memcache.Interface) memcache.Interface { gbf.Interface = mc return gbf }) mc = memcache.Get(ctx) err := TryWithLock(ctx, "testkey", "id", func(ctx context.Context) error { // simulate waiting for 64*delay time, and ensuring that checkLoop // runs that many times. for i := 0; i < 64; i++ { <-blocker clk.Add(delay) } gbf.Lock() mc.Set(mc.NewItem(key).SetValue([]byte("wat"))) gbf.Unlock() waitFalse(ctx) return nil }) So(err, ShouldBeNil) }) Convey("an empty context id is an error", func() { So(TryWithLock(ctx, "testkey", "", nil), ShouldEqual, ErrEmptyClientID) }) }) }
func TestMemcache(t *testing.T) { t.Parallel() Convey("memcache", t, func() { now := time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC) c, tc := testclock.UseTime(context.Background(), now) c = Use(c) mc := mcS.Get(c) Convey("implements MCSingleReadWriter", func() { Convey("Add", func() { itm := (mc.NewItem("sup"). SetValue([]byte("cool")). SetExpiration(time.Second)) So(mc.Add(itm), ShouldBeNil) Convey("which rejects objects already there", func() { So(mc.Add(itm), ShouldEqual, mcS.ErrNotStored) }) }) Convey("Get", func() { itm := &mcItem{ key: "sup", value: []byte("cool"), expiration: time.Second, } So(mc.Add(itm), ShouldBeNil) testItem := &mcItem{ key: "sup", value: []byte("cool"), CasID: 1, } getItm, err := mc.Get("sup") So(err, ShouldBeNil) So(getItm, ShouldResemble, testItem) Convey("which can expire", func() { tc.Add(time.Second * 4) getItm, err := mc.Get("sup") So(err, ShouldEqual, mcS.ErrCacheMiss) So(getItm, ShouldResemble, &mcItem{key: "sup"}) }) }) Convey("Delete", func() { Convey("works if it's there", func() { itm := &mcItem{ key: "sup", value: []byte("cool"), expiration: time.Second, } So(mc.Add(itm), ShouldBeNil) So(mc.Delete("sup"), ShouldBeNil) _, err := mc.Get("sup") So(err, ShouldEqual, mcS.ErrCacheMiss) }) Convey("but not if it's not there", func() { So(mc.Delete("sup"), ShouldEqual, mcS.ErrCacheMiss) }) }) Convey("Set", func() { itm := &mcItem{ key: "sup", value: []byte("cool"), expiration: time.Second, } So(mc.Add(itm), ShouldBeNil) itm.SetValue([]byte("newp")) So(mc.Set(itm), ShouldBeNil) testItem := &mcItem{ key: "sup", value: []byte("newp"), CasID: 2, } getItm, err := mc.Get("sup") So(err, ShouldBeNil) So(getItm, ShouldResemble, testItem) Convey("Flush works too", func() { So(mc.Flush(), ShouldBeNil) _, err := mc.Get("sup") So(err, ShouldEqual, mcS.ErrCacheMiss) }) }) Convey("Set (nil) is equivalent to Set([]byte{})", func() { So(mc.Set(mc.NewItem("bob")), ShouldBeNil) bob, err := mc.Get("bob") So(err, ShouldBeNil) So(bob.Value(), ShouldResemble, []byte{}) }) Convey("Increment", func() { val, err := mc.Increment("num", 7, 2) So(err, ShouldBeNil) So(val, ShouldEqual, 9) Convey("IncrementExisting", func() { val, err := mc.IncrementExisting("num", -2) So(err, ShouldBeNil) So(val, ShouldEqual, 7) val, err = mc.IncrementExisting("num", -100) So(err, ShouldBeNil) So(val, ShouldEqual, 0) _, err = mc.IncrementExisting("noexist", 2) So(err, ShouldEqual, mcS.ErrCacheMiss) So(mc.Set(mc.NewItem("text").SetValue([]byte("hello world, hooman!"))), ShouldBeNil) _, err = mc.IncrementExisting("text", 2) So(err.Error(), ShouldContainSubstring, "got invalid current value") }) }) Convey("CompareAndSwap", func() { itm := mcS.Item(&mcItem{ key: "sup", value: []byte("cool"), expiration: time.Second * 2, }) So(mc.Add(itm), ShouldBeNil) Convey("works after a Get", func() { itm, err := mc.Get("sup") So(err, ShouldBeNil) So(itm.(*mcItem).CasID, ShouldEqual, 1) itm.SetValue([]byte("newp")) So(mc.CompareAndSwap(itm), ShouldBeNil) }) Convey("but fails if you don't", func() { itm.SetValue([]byte("newp")) So(mc.CompareAndSwap(itm), ShouldEqual, mcS.ErrCASConflict) }) Convey("and fails if the item is expired/gone", func() { tc.Add(3 * time.Second) itm.SetValue([]byte("newp")) So(mc.CompareAndSwap(itm), ShouldEqual, mcS.ErrNotStored) }) }) }) Convey("check that the internal implementation is sane", func() { curTime := now err := mc.Add(&mcItem{ key: "sup", value: []byte("cool"), expiration: time.Second * 2, }) for i := 0; i < 4; i++ { _, err := mc.Get("sup") So(err, ShouldBeNil) } _, err = mc.Get("wot") So(err, ShouldErrLike, mcS.ErrCacheMiss) mci := mc.Raw().(*memcacheImpl) stats, err := mc.Stats() So(err, ShouldBeNil) So(stats.Items, ShouldEqual, 1) So(stats.Bytes, ShouldEqual, 4) So(stats.Hits, ShouldEqual, 4) So(stats.Misses, ShouldEqual, 1) So(stats.ByteHits, ShouldEqual, 4*4) So(mci.data.casID, ShouldEqual, 1) So(mci.data.items["sup"], ShouldResemble, &mcDataItem{ value: []byte("cool"), expiration: curTime.Add(time.Second * 2).Truncate(time.Second), casID: 1, }) getItm, err := mc.Get("sup") So(err, ShouldBeNil) So(len(mci.data.items), ShouldEqual, 1) So(mci.data.casID, ShouldEqual, 1) testItem := &mcItem{ key: "sup", value: []byte("cool"), CasID: 1, } So(getItm, ShouldResemble, testItem) }) }) }
// Test a Meter instance. func TestMeter(t *testing.T) { t.Parallel() Convey(`In a test environment`, t, func() { ctx, tc := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) // Setup a channel to acknowledge work for test synchronization. We will use // the work callback to trigger this channel. workC := make(chan interface{}) workAckC := make(chan interface{}) config := Config{ Callback: func(bundle []interface{}) { workC <- bundle }, IngestCallback: func(work interface{}) bool { workAckC <- work return false }, } Convey(`Will panic if no callback is supplied.`, func() { config.Callback = nil So(func() { New(ctx, config) }, ShouldPanic) }) Convey(`Timer: Will buffer work until the timer is signalled.`, func() { config.Delay = 1 * time.Second // Doesn't matter, non-zero will cause timer to be used. m := New(ctx, config) defer m.Stop() // Send three messages. None of them should be forwarded to the underlying // Output. for _, v := range []int{0, 1, 2} { m.AddWait(v) <-workAckC } // Signal our timer. tc.Add(1 * time.Second) // All three messages should be sent to our underlying Output at the same // time. So(<-workC, shouldHaveWork, 0, 1, 2) }) Convey(`Flush: Will buffer messages until a flush is triggered.`, func() { m := New(ctx, config) // Will never auto-flush. defer m.Stop() // Send two messages. Nothing should be forwarded. m.AddWait(0) <-workAckC m.AddWait(1) <-workAckC // Send a third Work unit. We should receive a bundle of {0, 1, 2}. m.AddWait(2) <-workAckC m.Flush() So(<-workC, shouldHaveWork, 0, 1, 2) }) Convey(`Count: Will buffer messages until count is reached.`, func() { config.Count = 3 m := New(ctx, config) defer m.Stop() // Send two messages. Nothing should be forwarded. m.AddWait(0) <-workAckC m.AddWait(1) <-workAckC // Send a third message. Our underlying Output should receive the set of // three. m.AddWait(2) <-workAckC So(<-workC, shouldHaveWork, 0, 1, 2) }) Convey(`WorkCallback: Will buffer messages until flushed via callback.`, func() { count := 0 config.IngestCallback = func(interface{}) bool { count++ return count >= 3 } m := New(ctx, config) defer m.Stop() m.AddWait(0) m.AddWait(1) m.AddWait(2) So(<-workC, shouldHaveWork, 0, 1, 2) }) Convey(`Configured with multiple constraints`, func() { config.Delay = 1 * time.Second // Doesn't matter, non-zero will cause timer to be used. config.Count = 3 m := New(ctx, config) defer m.Stop() // Fill our buckets up to near threshold without dumping messages. m.AddWait(0) <-workAckC m.AddWait(1) <-workAckC // Hit count thresholds and flush at the same time. m.AddWait(2) <-workAckC m.Flush() So(<-workC, shouldHaveWork, 0, 1, 2) // Fill our buckets up to near threshold again. m.AddWait(3) <-workAckC m.AddWait(4) <-workAckC // Hit time threshold. tc.Add(1 * time.Second) So(<-workC, shouldHaveWork, 3, 4) // Hit count threshold. m.AddWait(5) <-workAckC m.AddWait(6) <-workAckC m.AddWait(7) <-workAckC So(<-workC, shouldHaveWork, 5, 6, 7) }) Convey(`When full, will return ErrFull if not blocking.`, func() { m := newImpl(ctx, &config) // Fill up the work channel (do not reap workC). id := 0 for i := 0; i < config.getAddBufferSize(); i++ { So(m.Add(i), ShouldBeNil) id++ } // Add another work unit. So(m.Add(id+1), ShouldEqual, ErrFull) }) }) }
// TestEndpoint tests the endpoint implementation and API. func TestEndpointService(t *testing.T) { t.Parallel() Convey(`An endpoint service connected to a testing HTTP server`, t, func() { ctx, tc := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) // Retry up to ten times without delaying. ctx = context.WithValue(ctx, backoffPolicyKey, func() retry.Iterator { return &retry.Limited{Retries: 10} }) h := &testEndpointServiceHandler{} srv := httptest.NewTLSServer(h) defer srv.Close() tr := &http.Transport{ TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, } client := &http.Client{ Transport: tr, } c := &endpointServiceImpl{ endpointConfig: endpointConfig{url: srv.URL}, client: client, } msg := bytes.Repeat([]byte{0x60, 0x0d, 0xd0, 0x65}, 32) Convey(`Successfully posts a message.`, func() { So(c.send(ctx, msg), ShouldBeNil) So(h.connections, ShouldEqual, 1) So(h.errors, ShouldEqual, 0) So(h.messages, ShouldResemble, [][]byte{msg}) }) Convey(`Retries sending when an error is encountered.`, func() { tc.SetTimerCallback(func(t clock.Timer) { tc.Add(time.Second) }) h.failures = 4 So(c.send(ctx, msg), ShouldBeNil) So(h.connections, ShouldEqual, 5) So(h.errors, ShouldEqual, 4) So(h.messages, ShouldResemble, [][]byte{msg}) }) Convey(`Returns a transient error when a send completely fails.`, func() { h.failures = 11 err := c.send(ctx, msg) So(err, ShouldNotBeNil) So(errors.IsTransient(err), ShouldBeTrue) So(h.connections, ShouldEqual, 11) So(h.errors, ShouldEqual, 11) So(h.messages, ShouldResemble, [][]byte(nil)) }) }) }
func TestBuffer(t *testing.T) { t.Parallel() Convey(`A Buffer instance`, t, func() { ctx, _ := testclock.UseTime(context.Background(), time.Date(2015, 1, 1, 0, 0, 0, 0, time.UTC)) entriesC := make(chan []*Entry, 1) client := &testClient{ callback: func(entries []*Entry) error { entriesC <- entries return nil }, } options := BufferOptions{ Retry: func() retry.Iterator { return &retry.Limited{ Retries: 5, } }, } b := NewBuffer(ctx, options, client).(*bufferImpl) defer b.StopAndFlush() // Allow synchronization when a log entry is ingested. Set "ackC" to nil to // disable synchronization. ackC := make(chan *Entry) b.testLogCallback = func(e *Entry) { if ackC != nil { ackC <- e } } So(b.BatchSize, ShouldEqual, DefaultBatchSize) Convey(`Will push a single entry.`, func() { ackC = nil err := b.PushEntries([]*Entry{ { InsertID: "a", }, }) So(err, ShouldBeNil) entries := <-entriesC So(len(entries), ShouldEqual, 1) So(entries[0], ShouldResemble, &Entry{ InsertID: "a", }) So(client.pushes, ShouldEqual, 1) }) Convey(`Will batch logging data.`, func() { // The first message will be read immediately. err := b.PushEntries([]*Entry{ { InsertID: "a", }, }) So(err, ShouldBeNil) <-ackC // The next set of messages will be batched, since we haven't allowed our // client stub to finish its PushEntries. entries := make([]*Entry, b.BatchSize) for i := range entries { entries[i] = &Entry{ InsertID: fmt.Sprintf("%d", i), } } err = b.PushEntries(entries) So(err, ShouldBeNil) // Read the first bundle. bundle := <-entriesC So(len(bundle), ShouldEqual, 1) So(bundle[0].InsertID, ShouldEqual, "a") // Read the second bundle. for _ = range entries { <-ackC } bundle = <-entriesC So(len(bundle), ShouldEqual, b.BatchSize) for i := range bundle { So(bundle[i].InsertID, ShouldEqual, fmt.Sprintf("%d", i)) } So(client.pushes, ShouldEqual, 2) }) Convey(`Will retry on failure.`, func() { ackC = nil failures := 3 client.callback = func(entries []*Entry) error { if failures > 0 { failures-- return errors.New("test: induced failure") } entriesC <- entries return nil } err := b.PushEntries([]*Entry{ { InsertID: "a", }, }) So(err, ShouldBeNil) entries := <-entriesC So(len(entries), ShouldEqual, 1) So(entries[0], ShouldResemble, &Entry{ InsertID: "a", }) So(client.pushes, ShouldEqual, 4) }) }) Convey(`A Buffer instance configured to retry forever will stop if aborted.`, t, func() { entriesC := make(chan []*Entry) client := &testClient{ callback: func(entries []*Entry) error { entriesC <- entries return errors.New("test: failure") }, } options := BufferOptions{ Retry: func() retry.Iterator { return infiniteRetryIterator{} }, } b := NewBuffer(context.Background(), options, client) err := b.PushEntries([]*Entry{ { InsertID: "a", }, }) So(err, ShouldBeNil) // Wait for the buffer to finish. finishedC := make(chan struct{}) go func() { defer close(finishedC) b.StopAndFlush() }() // Make sure at least one attempt has been made. <-entriesC go func() { // Consume any other attempts. for _ = range entriesC { } }() // Abort the buffer. b.Abort() // Make sure at least one attempt has been made after the abort. <-entriesC // Assert that it will stop eventually. Rather than deadlock/panic, we wait // one real second and fail if it didn't terminate. Since there is no // underlying latency, one second (in the failure case) is acceptable. closed := false select { case <-finishedC: closed = true case <-time.After(1 * time.Second): break } So(closed, ShouldBeTrue) }) }