Beispiel #1
0
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)
		})
	})
}
Beispiel #2
0
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"}`,
			})
		})
	})
}
Beispiel #3
0
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)
		})
	})
}
Beispiel #4
0
// 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)
		})
	})
}
Beispiel #6
0
// 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
}
Beispiel #7
0
// 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
}
Beispiel #8
0
// 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
}
Beispiel #9
0
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)
			})

		})
	})
}
Beispiel #10
0
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),
					})
				}
			})
		})
	})
}
Beispiel #11
0
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)
		})
	})
}
Beispiel #12
0
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)
		})

	})
}
Beispiel #13
0
// 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)
		})
	})
}
Beispiel #14
0
// 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))
		})
	})
}
Beispiel #15
0
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)
	})
}