diff --git a/v2/delay/delay.go b/v2/delay/delay.go index 8afb7f62..7194b6c2 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 @@ func fileKey(file string) (string, error) { 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 @@ func Func(key string, i interface{}) *Function { // 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 @@ func Func(key string, i interface{}) *Function { } 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 ed267420..7708befc 100644 --- a/v2/delay/delay_test.go +++ b/v2/delay/delay_test.go @@ -42,17 +42,17 @@ func init() { } var ( - invalidFunc = Func("invalid", func() {}) - - regFuncRuns = 0 - regFuncMsg = "" - regFunc = Func("reg", func(c context.Context, arg string) { - regFuncRuns++ - regFuncMsg = arg - }) + regFRuns = 0 + regFMsg = "" + regF = func(c context.Context, arg string) { + regFRuns++ + regFMsg = arg + } + regFunc = Func("regFunc", regF) + regRegister = MustRegister("regRegister", regF) - 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 @@ var ( if ci != nil { b = ci.N() } - custFuncTally += a + b - }) + custFTally += a + b + } + custFunc = Func("custFunc", custF) + custRegister = MustRegister("custRegister", custF) - anotherCustFunc = Func("cust2", func(c context.Context, n int, ct *CustomType, ci CustomInterface) { + anotherCustFunc = Func("custFunc2", 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 (f *fakeContext) logf(level int64, format string, args ...interface{}) { 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 TestInvalidFunction(t *testing.T) { func TestVariadicFunctionArguments(t *testing.T) { // Check the argument type validation for variadic functions. - c := newFakeContext() calls := 0 @@ -153,15 +165,19 @@ func TestVariadicFunctionArguments(t *testing.T) { 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 @@ func TestBadArguments(t *testing.T) { 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 @@ func TestRunningFunction(t *testing.T) { 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 @@ func TestCustomType(t *testing.T) { 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 @@ func TestRunningVariadic(t *testing.T) { 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 @@ func TestErrorFunction(t *testing.T) { return tk, nil } - errFunc.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) - } - 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) - } + for _, testTarget := range []*Function{errFunc, errRegister} { + // reset state + c.logging = [][]interface{}{} + errFRuns = 0 + testTarget.Call(c.ctx) - wantLogging := [][]interface{}{ - {"ERROR", "delay: func failed (will retry): %v", errFuncErr}, + // 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) } - if !reflect.DeepEqual(c.logging, wantLogging) { - t.Errorf("Incorrect logging: got %+v, want %+v", c.logging, wantLogging) + { + 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", 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 TestDuplicateFunction(t *testing.T) { } } +func TestMustRegisterDuplicateFunction(t *testing.T) { + MustRegister("dupe", dupe1F) + defer func() { + err := recover() + if err == nil { + t.Error("MustRegister did not panic") + } + 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) +} + +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() {}) +} + + func TestGetRequestHeadersFromContext(t *testing.T) { - c := newFakeContext() + 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) - } + // 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) + // 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 } - task = tk - return tk, nil - } - reqFunc.Call(c.ctx) + testTarget.Call(c.ctx) - 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) + 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 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) + 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 @@ func TestStandardContext(t *testing.T) { 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) + } } }