Let users specify unique function keys using delay.MustRegister (#268)
diff --git a/v2/delay/delay.go b/v2/delay/delay.go
index 8afb7f6..7194b6c 100644
--- a/v2/delay/delay.go
+++ b/v2/delay/delay.go
@@ -3,52 +3,29 @@
// license that can be found in the LICENSE file.
/*
-Package delay provides a way to execute code outside the scope of a
-user request by using the taskqueue API.
-
-To declare a function that may be executed later, call Func
-in a top-level assignment context, passing it an arbitrary string key
-and a function whose first argument is of type context.Context.
-The key is used to look up the function so it can be called later.
- var laterFunc = delay.Func("key", myFunc)
-It is also possible to use a function literal.
- var laterFunc = delay.Func("key", func(c context.Context, x string) {
- // ...
- })
-
-To call a function, invoke its Call method.
- laterFunc.Call(c, "something")
-A function may be called any number of times. If the function has any
-return arguments, and the last one is of type error, the function may
-return a non-nil error to signal that the function should be retried.
-
-The arguments to functions may be of any type that is encodable by the gob
-package. If an argument is of interface type, it is the client's responsibility
-to register with the gob package whatever concrete type may be passed for that
-argument; see http://golang.org/pkg/gob/#Register for details.
-
-Any errors during initialization or execution of a function will be
-logged to the application logs. Error logs that occur during initialization will
-be associated with the request that invoked the Call method.
-
-The state of a function invocation that has not yet successfully
-executed is preserved by combining the file name in which it is declared
-with the string key that was passed to the Func function. Updating an app
-with pending function invocations should safe as long as the relevant
-functions have the (filename, key) combination preserved. The filename is
-parsed according to these rules:
- * Paths in package main are shortened to just the file name (github.com/foo/foo.go -> foo.go)
- * Paths are stripped to just package paths (/go/src/github.com/foo/bar.go -> github.com/foo/bar.go)
- * Module versions are stripped (/go/pkg/mod/github.com/foo/bar@v0.0.0-20181026220418-f595d03440dc/baz.go -> github.com/foo/bar/baz.go)
-
-There is some inherent risk of pending function invocations being lost during
-an update that contains large changes. For example, switching from using GOPATH
-to go.mod is a large change that may inadvertently cause file paths to change.
-
-The delay package uses the Task Queue API to create tasks that call the
-reserved application path "/_ah/queue/go/delay".
-This path must not be marked as "login: required" in app.yaml;
-it must be marked as "login: admin" or have no access restriction.
+Package delay provides a way to execute code outside of the scope of
+a user request by using the Task Queue API.
+To use a deferred function, you must register the function to be
+deferred as a top-level var. For example,
+ ```
+ var laterFunc = delay.MustRegister("key", myFunc)
+ func myFunc(ctx context.Context, a, b string) {...}
+ ```
+You can also inline with a function literal:
+ ```
+ var laterFunc = delay.MustRegister("key", func(ctx context.Context, a, b string) {...})
+ ```
+In the above example, "key" is a logical name for the function.
+The key needs to be globally unique across your entire application.
+To invoke the function in a deferred fashion, call the top-level item:
+ ```
+ laterFunc(ctx, "aaa", "bbb")
+ ```
+This will queue a task and return quickly; the function will be actually
+run in a new request. The delay package uses the Task Queue API to create
+tasks that call the reserved application path "/_ah/queue/go/delay".
+This path may only be marked as "login: admin" or have no access
+restriction; it will fail if marked as "login: required".
*/
package delay // import "google.golang.org/appengine/v2/delay"
@@ -151,17 +128,20 @@
return modVersionPat.ReplaceAllString(file, ""), nil
}
-// Func declares a new Function. The second argument must be a function with a
-// first argument of type context.Context.
-// This function must be called at program initialization time. That means it
-// must be called in a global variable declaration or from an init function.
-// This restriction is necessary because the instance that delays a function
-// call may not be the one that executes it. Only the code executed at program
-// initialization time is guaranteed to have been run by an instance before it
-// receives a request.
+// Func declares a new function that can be called in a deferred fashion.
+// The second argument i must be a function with the first argument of
+// type context.Context.
+// To make the key globally unique, the SDK code will combine "key" with
+// the filename of the file in which myFunc is defined
+// (e.g., /some/path/myfile.go). This is convenient, but can lead to
+// failed deferred tasks if you refactor your code, or change from
+// GOPATH to go.mod, and then re-deploy with in-flight deferred tasks.
+//
+// This function Func must be called in a global scope to properly
+// register the function with the framework.
+//
+// Deprecated: Use MustRegister instead.
func Func(key string, i interface{}) *Function {
- f := &Function{fv: reflect.ValueOf(i)}
-
// Derive unique, somewhat stable key for this func.
_, file, _, _ := runtime.Caller(1)
fk, err := fileKey(file)
@@ -169,16 +149,51 @@
// Not fatal, but log the error
stdlog.Printf("delay: %v", err)
}
- f.key = fk + ":" + key
+ key = fk + ":" + key
+ f, err := registerFunction(key, i)
+ if err != nil {
+ return f
+ }
+ if old := funcs[f.key]; old != nil {
+ old.err = fmt.Errorf("multiple functions registered for %s", key)
+ }
+ funcs[f.key] = f
+ return f
+}
+
+// MustRegister declares a new function that can be called in a deferred fashion.
+// The second argument i must be a function with the first argument of
+// type context.Context.
+// MustRegister requires the key to be globally unique.
+//
+// This function MustRegister must be called in a global scope to properly
+// register the function with the framework.
+// See the package notes above for more details.
+func MustRegister(key string, i interface{}) *Function {
+ f, err := registerFunction(key, i)
+ if err != nil {
+ panic(err)
+ }
+
+ if old := funcs[f.key]; old != nil {
+ panic(fmt.Errorf("multiple functions registered for %q", key))
+ }
+ funcs[f.key] = f
+ return f
+}
+
+func registerFunction(key string, i interface{}) (*Function, error) {
+ f := &Function{fv: reflect.ValueOf(i)}
+ f.key = key
t := f.fv.Type()
if t.Kind() != reflect.Func {
f.err = errors.New("not a function")
- return f
+ return f, f.err
}
if t.NumIn() == 0 || !isContext(t.In(0)) {
f.err = errFirstArg
- return f
+ return f, errFirstArg
}
// Register the function's arguments with the gob package.
@@ -194,12 +209,7 @@
}
gob.Register(reflect.Zero(t.In(i)).Interface())
}
-
- if old := funcs[f.key]; old != nil {
- old.err = fmt.Errorf("multiple functions registered for %s in %s", key, file)
- }
- funcs[f.key] = f
- return f
+ return f, nil
}
type invocation struct {
diff --git a/v2/delay/delay_test.go b/v2/delay/delay_test.go
index ed26742..7708bef 100644
--- a/v2/delay/delay_test.go
+++ b/v2/delay/delay_test.go
@@ -42,17 +42,17 @@
}
var (
- invalidFunc = Func("invalid", func() {})
+ regFRuns = 0
+ regFMsg = ""
+ regF = func(c context.Context, arg string) {
+ regFRuns++
+ regFMsg = arg
+ }
+ regFunc = Func("regFunc", regF)
+ regRegister = MustRegister("regRegister", regF)
- regFuncRuns = 0
- regFuncMsg = ""
- regFunc = Func("reg", func(c context.Context, arg string) {
- regFuncRuns++
- regFuncMsg = arg
- })
-
- custFuncTally = 0
- custFunc = Func("cust", func(c context.Context, ct *CustomType, ci CustomInterface) {
+ custFTally = 0
+ custF = func(c context.Context, ct *CustomType, ci CustomInterface) {
a, b := 2, 3
if ct != nil {
a = ct.N
@@ -60,56 +60,68 @@
if ci != nil {
b = ci.N()
}
- custFuncTally += a + b
+ custFTally += a + b
+ }
+ custFunc = Func("custFunc", custF)
+ custRegister = MustRegister("custRegister", custF)
+
+ anotherCustFunc = Func("custFunc2", func(c context.Context, n int, ct *CustomType, ci CustomInterface) {
})
- anotherCustFunc = Func("cust2", func(c context.Context, n int, ct *CustomType, ci CustomInterface) {
- })
-
- varFuncMsg = ""
- varFunc = Func("variadic", func(c context.Context, format string, args ...int) {
+ varFMsg = ""
+ varF = func(c context.Context, format string, args ...int) {
// convert []int to []interface{} for fmt.Sprintf.
as := make([]interface{}, len(args))
for i, a := range args {
as[i] = a
}
- varFuncMsg = fmt.Sprintf(format, as...)
- })
+ varFMsg = fmt.Sprintf(format, as...)
+ }
+ varFunc = Func("variadicFunc", varF)
+ varRegister = MustRegister("variadicRegister", varF)
- errFuncRuns = 0
- errFuncErr = errors.New("error!")
- errFunc = Func("err", func(c context.Context) error {
- errFuncRuns++
- if errFuncRuns == 1 {
+ errFRuns = 0
+ errFErr = errors.New("error!")
+ errF = func(c context.Context) error {
+ errFRuns++
+ if errFRuns == 1 {
return nil
}
- return errFuncErr
- })
+ return errFErr
+ }
+ errFunc = Func("errFunc", errF)
+ errRegister = MustRegister("errRegister", errF)
dupeWhich = 0
- dupe1Func = Func("dupe", func(c context.Context) {
+ dupe1F = func(c context.Context) {
if dupeWhich == 0 {
dupeWhich = 1
}
- })
- dupe2Func = Func("dupe", func(c context.Context) {
+ }
+ dupe1Func = Func("dupe", dupe1F)
+ dupe2F = func(c context.Context) {
if dupeWhich == 0 {
dupeWhich = 2
}
- })
+ }
+ dupe2Func = Func("dupe", dupe2F)
- reqFuncRuns = 0
- reqFuncHeaders *taskqueue.RequestHeaders
- reqFuncErr error
- reqFunc = Func("req", func(c context.Context) {
- reqFuncRuns++
- reqFuncHeaders, reqFuncErr = RequestHeaders(c)
- })
+ requestFuncRuns = 0
+ requestFuncHeaders *taskqueue.RequestHeaders
+ requestFuncErr error
+ requestF = func(c context.Context) {
+ requestFuncRuns++
+ requestFuncHeaders, requestFuncErr = RequestHeaders(c)
+ }
+ requestFunc = Func("requestFunc", requestF)
+ requestRegister = MustRegister("requestRegister", requestF)
stdCtxRuns = 0
- stdCtxFunc = Func("stdctx", func(c stdctx.Context) {
+ stdCtxF = func(c stdctx.Context) {
stdCtxRuns++
- })
+ }
+ stdCtxFunc = Func("stdctxFunc", stdCtxF)
+ stdCtxRegister = MustRegister("stdctxRegister", stdCtxF)
)
type fakeContext struct {
@@ -136,6 +148,7 @@
func TestInvalidFunction(t *testing.T) {
c := newFakeContext()
+ invalidFunc := Func("invalid", func() {})
if got, want := invalidFunc.Call(c.ctx), fmt.Errorf("delay: func is invalid: %s", errFirstArg); got.Error() != want.Error() {
t.Errorf("Incorrect error: got %q, want %q", got, want)
@@ -144,7 +157,6 @@
func TestVariadicFunctionArguments(t *testing.T) {
// Check the argument type validation for variadic functions.
-
c := newFakeContext()
calls := 0
@@ -153,15 +165,19 @@
return t, nil
}
- varFunc.Call(c.ctx, "hi")
- varFunc.Call(c.ctx, "%d", 12)
- varFunc.Call(c.ctx, "%d %d %d", 3, 1, 4)
- if calls != 3 {
- t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
- }
+ for _, testTarget := range []*Function{varFunc, varRegister} {
+ // reset state
+ calls = 0
+ testTarget.Call(c.ctx, "hi")
+ testTarget.Call(c.ctx, "%d", 12)
+ testTarget.Call(c.ctx, "%d %d %d", 3, 1, 4)
+ if calls != 3 {
+ t.Errorf("Got %d calls to taskqueueAdder, want 3", calls)
+ }
- if got, want := varFunc.Call(c.ctx, "%d %s", 12, "a string is bad"), errors.New("delay: argument 3 has wrong type: string is not assignable to int"); got.Error() != want.Error() {
- t.Errorf("Incorrect error: got %q, want %q", got, want)
+ if got, want := testTarget.Call(c.ctx, "%d %s", 12, "a string is bad"), errors.New("delay: argument 3 has wrong type: string is not assignable to int"); got.Error() != want.Error() {
+ t.Errorf("Incorrect error: got %q, want %q", got, want)
+ }
}
}
@@ -187,17 +203,18 @@
wantErr: "delay: argument 1 has wrong type: int is not assignable to string",
},
}
- for i, tc := range tests {
- got := regFunc.Call(c.ctx, tc.args...)
- if got.Error() != tc.wantErr {
- t.Errorf("Call %v: got %q, want %q", i, got, tc.wantErr)
+ for _, testTarget := range []*Function{regFunc, regRegister} {
+ for i, tc := range tests {
+ got := testTarget.Call(c.ctx, tc.args...)
+ if got.Error() != tc.wantErr {
+ t.Errorf("Call %v: got %q, want %q", i, got, tc.wantErr)
+ }
}
}
}
func TestRunningFunction(t *testing.T) {
c := newFakeContext()
-
// Fake out the adding of a task.
var task *taskqueue.Task
taskqueueAdder = func(_ context.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
@@ -208,23 +225,25 @@
return tk, nil
}
- regFuncRuns, regFuncMsg = 0, "" // reset state
- const msg = "Why, hello!"
- regFunc.Call(c.ctx, msg)
+ for _, testTarget := range []*Function{regFunc, regRegister} {
+ regFRuns, regFMsg = 0, "" // reset state
+ const msg = "Why, hello!"
+ testTarget.Call(c.ctx, msg)
- // Simulate the Task Queue service.
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
+ // Simulate the Task Queue service.
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
- if regFuncRuns != 1 {
- t.Errorf("regFuncRuns: got %d, want 1", regFuncRuns)
- }
- if regFuncMsg != msg {
- t.Errorf("regFuncMsg: got %q, want %q", regFuncMsg, msg)
+ if regFRuns != 1 {
+ t.Errorf("regFuncRuns: got %d, want 1", regFRuns)
+ }
+ if regFMsg != msg {
+ t.Errorf("regFuncMsg: got %q, want %q", regFMsg, msg)
+ }
}
}
@@ -241,36 +260,38 @@
return tk, nil
}
- custFuncTally = 0 // reset state
- custFunc.Call(c.ctx, &CustomType{N: 11}, CustomImpl(13))
+ for _, testTarget := range []*Function{custFunc, custRegister} {
+ custFTally = 0 // reset state
+ testTarget.Call(c.ctx, &CustomType{N: 11}, CustomImpl(13))
- // Simulate the Task Queue service.
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
+ // Simulate the Task Queue service.
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
- if custFuncTally != 24 {
- t.Errorf("custFuncTally = %d, want 24", custFuncTally)
- }
+ if custFTally != 24 {
+ t.Errorf("custFTally = %d, want 24", custFTally)
+ }
- // Try the same, but with nil values; one is a nil pointer (and thus a non-nil interface value),
- // and the other is a nil interface value.
- custFuncTally = 0 // reset state
- custFunc.Call(c.ctx, (*CustomType)(nil), nil)
+ // Try the same, but with nil values; one is a nil pointer (and thus a non-nil interface value),
+ // and the other is a nil interface value.
+ custFTally = 0 // reset state
+ testTarget.Call(c.ctx, (*CustomType)(nil), nil)
- // Simulate the Task Queue service.
- req, err = http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw = httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
+ // Simulate the Task Queue service.
+ req, err = http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw = httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
- if custFuncTally != 5 {
- t.Errorf("custFuncTally = %d, want 5", custFuncTally)
+ if custFTally != 5 {
+ t.Errorf("custFTally = %d, want 5", custFTally)
+ }
}
}
@@ -287,20 +308,22 @@
return tk, nil
}
- varFuncMsg = "" // reset state
- varFunc.Call(c.ctx, "Amiga %d has %d KB RAM", 500, 512)
+ for _, testTarget := range []*Function{varFunc, varRegister} {
+ varFMsg = "" // reset state
+ testTarget.Call(c.ctx, "Amiga %d has %d KB RAM", 500, 512)
- // Simulate the Task Queue service.
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
+ // Simulate the Task Queue service.
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
- const expected = "Amiga 500 has 512 KB RAM"
- if varFuncMsg != expected {
- t.Errorf("varFuncMsg = %q, want %q", varFuncMsg, expected)
+ const expected = "Amiga 500 has 512 KB RAM"
+ if varFMsg != expected {
+ t.Errorf("varFMsg = %q, want %q", varFMsg, expected)
+ }
}
}
@@ -317,39 +340,44 @@
return tk, nil
}
- errFunc.Call(c.ctx)
+ for _, testTarget := range []*Function{errFunc, errRegister} {
+ // reset state
+ c.logging = [][]interface{}{}
+ errFRuns = 0
+ testTarget.Call(c.ctx)
- // Simulate the Task Queue service.
- // The first call should succeed; the second call should fail.
- {
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
+ // Simulate the Task Queue service.
+ // The first call should succeed; the second call should fail.
+ {
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
}
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
- }
- {
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
- if rw.Code != http.StatusInternalServerError {
- t.Errorf("Got status code %d, want %d", rw.Code, http.StatusInternalServerError)
- }
+ {
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
+ if rw.Code != http.StatusInternalServerError {
+ t.Errorf("Got status code %d, want %d", rw.Code, http.StatusInternalServerError)
+ }
- wantLogging := [][]interface{}{
- {"ERROR", "delay: func failed (will retry): %v", errFuncErr},
- }
- if !reflect.DeepEqual(c.logging, wantLogging) {
- t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+ wantLogging := [][]interface{}{
+ {"ERROR", "delay: func failed (will retry): %v", errFErr},
+ }
+ if !reflect.DeepEqual(c.logging, wantLogging) {
+ t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging)
+ }
}
}
}
-func TestDuplicateFunction(t *testing.T) {
+func TestFuncDuplicateFunction(t *testing.T) {
c := newFakeContext()
// Fake out the adding of a task.
@@ -390,48 +418,80 @@
}
}
-func TestGetRequestHeadersFromContext(t *testing.T) {
- c := newFakeContext()
-
- // Outside a delay.Func should return an error.
- headers, err := RequestHeaders(c.ctx)
- if headers != nil {
- t.Errorf("RequestHeaders outside Func, got %v, want nil", headers)
- }
- if err != errOutsideDelayFunc {
- t.Errorf("RequestHeaders outside Func err, got %v, want %v", err, errOutsideDelayFunc)
- }
-
- // Fake out the adding of a task.
- var task *taskqueue.Task
- taskqueueAdder = func(_ context.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
- if queue != "" {
- t.Errorf(`Got queue %q, expected ""`, queue)
+func TestMustRegisterDuplicateFunction(t *testing.T) {
+ MustRegister("dupe", dupe1F)
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Error("MustRegister did not panic")
}
- task = tk
- return tk, nil
- }
+ got := fmt.Sprintf("%s", err)
+ want := fmt.Sprintf("multiple functions registered for %q", "dupe")
+ if got != want {
+ t.Errorf("Incorrect error: got %q, want %q", got, want)
+ }
+ }()
+ MustRegister("dupe", dupe2F)
+}
- reqFunc.Call(c.ctx)
+func TestInvalidFunction_MustRegister(t *testing.T) {
+ defer func() {
+ err := recover()
+ if err == nil {
+ t.Error("MustRegister did not panic")
+ }
+ if err != errFirstArg {
+ t.Errorf("Incorrect error: got %q, want %q", err, errFirstArg)
+ }
+ }()
+ MustRegister("invalid", func() {})
+}
- reqFuncRuns, reqFuncHeaders = 0, nil // reset state
- // Simulate the Task Queue service.
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- req.Header.Set("x-appengine-taskname", "foobar")
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
- if reqFuncRuns != 1 {
- t.Errorf("reqFuncRuns: got %d, want 1", reqFuncRuns)
- }
- if reqFuncHeaders.TaskName != "foobar" {
- t.Errorf("reqFuncHeaders.TaskName: got %v, want 'foobar'", reqFuncHeaders.TaskName)
- }
- if reqFuncErr != nil {
- t.Errorf("reqFuncErr: got %v, want nil", reqFuncErr)
+func TestGetRequestHeadersFromContext(t *testing.T) {
+ for _, testTarget := range []*Function{requestFunc, requestRegister} {
+ c := newFakeContext()
+
+ // Outside a delay.Func should return an error.
+ headers, err := RequestHeaders(c.ctx)
+ if headers != nil {
+ t.Errorf("RequestHeaders outside Func, got %v, want nil", headers)
+ }
+ if err != errOutsideDelayFunc {
+ t.Errorf("RequestHeaders outside Func err, got %v, want %v", err, errOutsideDelayFunc)
+ }
+
+ // Fake out the adding of a task.
+ var task *taskqueue.Task
+ taskqueueAdder = func(_ context.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ testTarget.Call(c.ctx)
+
+ requestFuncRuns, requestFuncHeaders = 0, nil // reset state
+ // Simulate the Task Queue service.
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ req.Header.Set("x-appengine-taskname", "foobar")
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
+
+ if requestFuncRuns != 1 {
+ t.Errorf("requestFuncRuns: got %d, want 1", requestFuncRuns)
+ }
+ if requestFuncHeaders.TaskName != "foobar" {
+ t.Errorf("requestFuncHeaders.TaskName: got %v, want 'foobar'", requestFuncHeaders.TaskName)
+ }
+ if requestFuncErr != nil {
+ t.Errorf("requestFuncErr: got %v, want nil", requestFuncErr)
+ }
}
}
@@ -446,22 +506,24 @@
return tk, nil
}
- c := newFakeContext()
- stdCtxRuns = 0 // reset state
- if err := stdCtxFunc.Call(c.ctx); err != nil {
- t.Fatal("Function.Call:", err)
- }
+ for _, testTarget := range []*Function{stdCtxFunc, stdCtxRegister} {
+ c := newFakeContext()
+ stdCtxRuns = 0 // reset state
+ if err := testTarget.Call(c.ctx); err != nil {
+ t.Fatal("Function.Call:", err)
+ }
- // Simulate the Task Queue service.
- req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
- if err != nil {
- t.Fatalf("Failed making http.Request: %v", err)
- }
- rw := httptest.NewRecorder()
- runFunc(c.ctx, rw, req)
+ // Simulate the Task Queue service.
+ req, err := http.NewRequest("POST", path, bytes.NewBuffer(task.Payload))
+ if err != nil {
+ t.Fatalf("Failed making http.Request: %v", err)
+ }
+ rw := httptest.NewRecorder()
+ runFunc(c.ctx, rw, req)
- if stdCtxRuns != 1 {
- t.Errorf("stdCtxRuns: got %d, want 1", stdCtxRuns)
+ if stdCtxRuns != 1 {
+ t.Errorf("stdCtxRuns: got %d, want 1", stdCtxRuns)
+ }
}
}