summaryrefslogtreecommitdiffstats
path: root/vendor/google.golang.org/appengine/delay
diff options
context:
space:
mode:
authorChristopher Speller <crspeller@gmail.com>2018-01-29 14:17:40 -0800
committerGitHub <noreply@github.com>2018-01-29 14:17:40 -0800
commit961c04cae992eadb42d286d2f85f8a675bdc68c8 (patch)
tree3408f2d06f847e966c53485e2d54c692cdd037c1 /vendor/google.golang.org/appengine/delay
parent8d66523ba7d9a77129844be476732ebfd5272d64 (diff)
downloadchat-961c04cae992eadb42d286d2f85f8a675bdc68c8.tar.gz
chat-961c04cae992eadb42d286d2f85f8a675bdc68c8.tar.bz2
chat-961c04cae992eadb42d286d2f85f8a675bdc68c8.zip
Upgrading server dependancies (#8154)
Diffstat (limited to 'vendor/google.golang.org/appengine/delay')
-rw-r--r--vendor/google.golang.org/appengine/delay/delay.go295
-rw-r--r--vendor/google.golang.org/appengine/delay/delay_go17.go23
-rw-r--r--vendor/google.golang.org/appengine/delay/delay_go17_test.go55
-rw-r--r--vendor/google.golang.org/appengine/delay/delay_pre17.go19
-rw-r--r--vendor/google.golang.org/appengine/delay/delay_test.go428
5 files changed, 820 insertions, 0 deletions
diff --git a/vendor/google.golang.org/appengine/delay/delay.go b/vendor/google.golang.org/appengine/delay/delay.go
new file mode 100644
index 000000000..52915a422
--- /dev/null
+++ b/vendor/google.golang.org/appengine/delay/delay.go
@@ -0,0 +1,295 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// 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 is safe as long as the relevant
+functions have the (filename, key) combination preserved.
+
+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 // import "google.golang.org/appengine/delay"
+
+import (
+ "bytes"
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "net/http"
+ "reflect"
+ "runtime"
+
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine"
+ "google.golang.org/appengine/log"
+ "google.golang.org/appengine/taskqueue"
+)
+
+// Function represents a function that may have a delayed invocation.
+type Function struct {
+ fv reflect.Value // Kind() == reflect.Func
+ key string
+ err error // any error during initialization
+}
+
+const (
+ // The HTTP path for invocations.
+ path = "/_ah/queue/go/delay"
+ // Use the default queue.
+ queue = ""
+)
+
+type contextKey int
+
+var (
+ // registry of all delayed functions
+ funcs = make(map[string]*Function)
+
+ // precomputed types
+ errorType = reflect.TypeOf((*error)(nil)).Elem()
+
+ // errors
+ errFirstArg = errors.New("first argument must be context.Context")
+ errOutsideDelayFunc = errors.New("request headers are only available inside a delay.Func")
+
+ // context keys
+ headersContextKey contextKey = 0
+)
+
+// 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 Func(key string, i interface{}) *Function {
+ f := &Function{fv: reflect.ValueOf(i)}
+
+ // Derive unique, somewhat stable key for this func.
+ _, file, _, _ := runtime.Caller(1)
+ f.key = file + ":" + key
+
+ t := f.fv.Type()
+ if t.Kind() != reflect.Func {
+ f.err = errors.New("not a function")
+ return f
+ }
+ if t.NumIn() == 0 || !isContext(t.In(0)) {
+ f.err = errFirstArg
+ return f
+ }
+
+ // Register the function's arguments with the gob package.
+ // This is required because they are marshaled inside a []interface{}.
+ // gob.Register only expects to be called during initialization;
+ // that's fine because this function expects the same.
+ for i := 0; i < t.NumIn(); i++ {
+ // Only concrete types may be registered. If the argument has
+ // interface type, the client is resposible for registering the
+ // concrete types it will hold.
+ if t.In(i).Kind() == reflect.Interface {
+ continue
+ }
+ 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
+}
+
+type invocation struct {
+ Key string
+ Args []interface{}
+}
+
+// Call invokes a delayed function.
+// err := f.Call(c, ...)
+// is equivalent to
+// t, _ := f.Task(...)
+// _, err := taskqueue.Add(c, t, "")
+func (f *Function) Call(c context.Context, args ...interface{}) error {
+ t, err := f.Task(args...)
+ if err != nil {
+ return err
+ }
+ _, err = taskqueueAdder(c, t, queue)
+ return err
+}
+
+// Task creates a Task that will invoke the function.
+// Its parameters may be tweaked before adding it to a queue.
+// Users should not modify the Path or Payload fields of the returned Task.
+func (f *Function) Task(args ...interface{}) (*taskqueue.Task, error) {
+ if f.err != nil {
+ return nil, fmt.Errorf("delay: func is invalid: %v", f.err)
+ }
+
+ nArgs := len(args) + 1 // +1 for the context.Context
+ ft := f.fv.Type()
+ minArgs := ft.NumIn()
+ if ft.IsVariadic() {
+ minArgs--
+ }
+ if nArgs < minArgs {
+ return nil, fmt.Errorf("delay: too few arguments to func: %d < %d", nArgs, minArgs)
+ }
+ if !ft.IsVariadic() && nArgs > minArgs {
+ return nil, fmt.Errorf("delay: too many arguments to func: %d > %d", nArgs, minArgs)
+ }
+
+ // Check arg types.
+ for i := 1; i < nArgs; i++ {
+ at := reflect.TypeOf(args[i-1])
+ var dt reflect.Type
+ if i < minArgs {
+ // not a variadic arg
+ dt = ft.In(i)
+ } else {
+ // a variadic arg
+ dt = ft.In(minArgs).Elem()
+ }
+ // nil arguments won't have a type, so they need special handling.
+ if at == nil {
+ // nil interface
+ switch dt.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ continue // may be nil
+ }
+ return nil, fmt.Errorf("delay: argument %d has wrong type: %v is not nilable", i, dt)
+ }
+ switch at.Kind() {
+ case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
+ av := reflect.ValueOf(args[i-1])
+ if av.IsNil() {
+ // nil value in interface; not supported by gob, so we replace it
+ // with a nil interface value
+ args[i-1] = nil
+ }
+ }
+ if !at.AssignableTo(dt) {
+ return nil, fmt.Errorf("delay: argument %d has wrong type: %v is not assignable to %v", i, at, dt)
+ }
+ }
+
+ inv := invocation{
+ Key: f.key,
+ Args: args,
+ }
+
+ buf := new(bytes.Buffer)
+ if err := gob.NewEncoder(buf).Encode(inv); err != nil {
+ return nil, fmt.Errorf("delay: gob encoding failed: %v", err)
+ }
+
+ return &taskqueue.Task{
+ Path: path,
+ Payload: buf.Bytes(),
+ }, nil
+}
+
+// Request returns the special task-queue HTTP request headers for the current
+// task queue handler. Returns an error if called from outside a delay.Func.
+func RequestHeaders(c context.Context) (*taskqueue.RequestHeaders, error) {
+ if ret, ok := c.Value(headersContextKey).(*taskqueue.RequestHeaders); ok {
+ return ret, nil
+ }
+ return nil, errOutsideDelayFunc
+}
+
+var taskqueueAdder = taskqueue.Add // for testing
+
+func init() {
+ http.HandleFunc(path, func(w http.ResponseWriter, req *http.Request) {
+ runFunc(appengine.NewContext(req), w, req)
+ })
+}
+
+func runFunc(c context.Context, w http.ResponseWriter, req *http.Request) {
+ defer req.Body.Close()
+
+ c = context.WithValue(c, headersContextKey, taskqueue.ParseRequestHeaders(req.Header))
+
+ var inv invocation
+ if err := gob.NewDecoder(req.Body).Decode(&inv); err != nil {
+ log.Errorf(c, "delay: failed decoding task payload: %v", err)
+ log.Warningf(c, "delay: dropping task")
+ return
+ }
+
+ f := funcs[inv.Key]
+ if f == nil {
+ log.Errorf(c, "delay: no func with key %q found", inv.Key)
+ log.Warningf(c, "delay: dropping task")
+ return
+ }
+
+ ft := f.fv.Type()
+ in := []reflect.Value{reflect.ValueOf(c)}
+ for _, arg := range inv.Args {
+ var v reflect.Value
+ if arg != nil {
+ v = reflect.ValueOf(arg)
+ } else {
+ // Task was passed a nil argument, so we must construct
+ // the zero value for the argument here.
+ n := len(in) // we're constructing the nth argument
+ var at reflect.Type
+ if !ft.IsVariadic() || n < ft.NumIn()-1 {
+ at = ft.In(n)
+ } else {
+ at = ft.In(ft.NumIn() - 1).Elem()
+ }
+ v = reflect.Zero(at)
+ }
+ in = append(in, v)
+ }
+ out := f.fv.Call(in)
+
+ if n := ft.NumOut(); n > 0 && ft.Out(n-1) == errorType {
+ if errv := out[n-1]; !errv.IsNil() {
+ log.Errorf(c, "delay: func failed (will retry): %v", errv.Interface())
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ }
+}
diff --git a/vendor/google.golang.org/appengine/delay/delay_go17.go b/vendor/google.golang.org/appengine/delay/delay_go17.go
new file mode 100644
index 000000000..9a59e8b0d
--- /dev/null
+++ b/vendor/google.golang.org/appengine/delay/delay_go17.go
@@ -0,0 +1,23 @@
+// Copyright 2017 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+//+build go1.7
+
+package delay
+
+import (
+ stdctx "context"
+ "reflect"
+
+ netctx "golang.org/x/net/context"
+)
+
+var (
+ stdContextType = reflect.TypeOf((*stdctx.Context)(nil)).Elem()
+ netContextType = reflect.TypeOf((*netctx.Context)(nil)).Elem()
+)
+
+func isContext(t reflect.Type) bool {
+ return t == stdContextType || t == netContextType
+}
diff --git a/vendor/google.golang.org/appengine/delay/delay_go17_test.go b/vendor/google.golang.org/appengine/delay/delay_go17_test.go
new file mode 100644
index 000000000..0e708d005
--- /dev/null
+++ b/vendor/google.golang.org/appengine/delay/delay_go17_test.go
@@ -0,0 +1,55 @@
+// Copyright 2017 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+//+build go1.7
+
+package delay
+
+import (
+ "bytes"
+ stdctx "context"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ netctx "golang.org/x/net/context"
+ "google.golang.org/appengine/taskqueue"
+)
+
+var (
+ stdCtxRuns = 0
+ stdCtxFunc = Func("stdctx", func(c stdctx.Context) {
+ stdCtxRuns++
+ })
+)
+
+func TestStandardContext(t *testing.T) {
+ // Fake out the adding of a task.
+ var task *taskqueue.Task
+ taskqueueAdder = func(_ netctx.Context, tk *taskqueue.Task, queue string) (*taskqueue.Task, error) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ c := newFakeContext()
+ stdCtxRuns = 0 // reset state
+ if err := stdCtxFunc.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)
+
+ if stdCtxRuns != 1 {
+ t.Errorf("stdCtxRuns: got %d, want 1", stdCtxRuns)
+ }
+}
diff --git a/vendor/google.golang.org/appengine/delay/delay_pre17.go b/vendor/google.golang.org/appengine/delay/delay_pre17.go
new file mode 100644
index 000000000..d30c75dfb
--- /dev/null
+++ b/vendor/google.golang.org/appengine/delay/delay_pre17.go
@@ -0,0 +1,19 @@
+// Copyright 2017 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+//+build !go1.7
+
+package delay
+
+import (
+ "reflect"
+
+ "golang.org/x/net/context"
+)
+
+var contextType = reflect.TypeOf((*context.Context)(nil)).Elem()
+
+func isContext(t reflect.Type) bool {
+ return t == contextType
+}
diff --git a/vendor/google.golang.org/appengine/delay/delay_test.go b/vendor/google.golang.org/appengine/delay/delay_test.go
new file mode 100644
index 000000000..3df2bf7e3
--- /dev/null
+++ b/vendor/google.golang.org/appengine/delay/delay_test.go
@@ -0,0 +1,428 @@
+// Copyright 2011 Google Inc. All rights reserved.
+// Use of this source code is governed by the Apache 2.0
+// license that can be found in the LICENSE file.
+
+package delay
+
+import (
+ "bytes"
+ "encoding/gob"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+
+ "github.com/golang/protobuf/proto"
+ "golang.org/x/net/context"
+
+ "google.golang.org/appengine/internal"
+ "google.golang.org/appengine/taskqueue"
+)
+
+type CustomType struct {
+ N int
+}
+
+type CustomInterface interface {
+ N() int
+}
+
+type CustomImpl int
+
+func (c CustomImpl) N() int { return int(c) }
+
+// CustomImpl needs to be registered with gob.
+func init() {
+ gob.Register(CustomImpl(0))
+}
+
+var (
+ invalidFunc = Func("invalid", func() {})
+
+ 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) {
+ a, b := 2, 3
+ if ct != nil {
+ a = ct.N
+ }
+ if ci != nil {
+ b = ci.N()
+ }
+ custFuncTally += a + b
+ })
+
+ 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) {
+ // 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...)
+ })
+
+ errFuncRuns = 0
+ errFuncErr = errors.New("error!")
+ errFunc = Func("err", func(c context.Context) error {
+ errFuncRuns++
+ if errFuncRuns == 1 {
+ return nil
+ }
+ return errFuncErr
+ })
+
+ dupeWhich = 0
+ dupe1Func = Func("dupe", func(c context.Context) {
+ if dupeWhich == 0 {
+ dupeWhich = 1
+ }
+ })
+ dupe2Func = Func("dupe", func(c context.Context) {
+ if dupeWhich == 0 {
+ dupeWhich = 2
+ }
+ })
+
+ reqFuncRuns = 0
+ reqFuncHeaders *taskqueue.RequestHeaders
+ reqFuncErr error
+ reqFunc = Func("req", func(c context.Context) {
+ reqFuncRuns++
+ reqFuncHeaders, reqFuncErr = RequestHeaders(c)
+ })
+)
+
+type fakeContext struct {
+ ctx context.Context
+ logging [][]interface{}
+}
+
+func newFakeContext() *fakeContext {
+ f := new(fakeContext)
+ f.ctx = internal.WithCallOverride(context.Background(), f.call)
+ f.ctx = internal.WithLogOverride(f.ctx, f.logf)
+ return f
+}
+
+func (f *fakeContext) call(ctx context.Context, service, method string, in, out proto.Message) error {
+ panic("should never be called")
+}
+
+var logLevels = map[int64]string{1: "INFO", 3: "ERROR"}
+
+func (f *fakeContext) logf(level int64, format string, args ...interface{}) {
+ f.logging = append(f.logging, append([]interface{}{logLevels[level], format}, args...))
+}
+
+func TestInvalidFunction(t *testing.T) {
+ c := newFakeContext()
+
+ 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)
+ }
+}
+
+func TestVariadicFunctionArguments(t *testing.T) {
+ // Check the argument type validation for variadic functions.
+
+ c := newFakeContext()
+
+ calls := 0
+ taskqueueAdder = func(c context.Context, t *taskqueue.Task, _ string) (*taskqueue.Task, error) {
+ calls++
+ 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)
+ }
+
+ 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)
+ }
+}
+
+func TestBadArguments(t *testing.T) {
+ // Try running regFunc with different sets of inappropriate arguments.
+
+ c := newFakeContext()
+
+ tests := []struct {
+ args []interface{} // all except context
+ wantErr string
+ }{
+ {
+ args: nil,
+ wantErr: "delay: too few arguments to func: 1 < 2",
+ },
+ {
+ args: []interface{}{"lala", 53},
+ wantErr: "delay: too many arguments to func: 3 > 2",
+ },
+ {
+ args: []interface{}{53},
+ 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)
+ }
+ }
+}
+
+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) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ regFuncRuns, regFuncMsg = 0, "" // reset state
+ const msg = "Why, hello!"
+ regFunc.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)
+
+ if regFuncRuns != 1 {
+ t.Errorf("regFuncRuns: got %d, want 1", regFuncRuns)
+ }
+ if regFuncMsg != msg {
+ t.Errorf("regFuncMsg: got %q, want %q", regFuncMsg, msg)
+ }
+}
+
+func TestCustomType(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) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ custFuncTally = 0 // reset state
+ custFunc.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)
+
+ if custFuncTally != 24 {
+ t.Errorf("custFuncTally = %d, want 24", custFuncTally)
+ }
+
+ // 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)
+
+ // 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)
+ }
+}
+
+func TestRunningVariadic(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) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ varFuncMsg = "" // reset state
+ varFunc.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)
+
+ const expected = "Amiga 500 has 512 KB RAM"
+ if varFuncMsg != expected {
+ t.Errorf("varFuncMsg = %q, want %q", varFuncMsg, expected)
+ }
+}
+
+func TestErrorFunction(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) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ 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)
+ }
+
+ 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)
+ }
+ }
+}
+
+func TestDuplicateFunction(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) {
+ if queue != "" {
+ t.Errorf(`Got queue %q, expected ""`, queue)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ if err := dupe1Func.Call(c.ctx); err == nil {
+ t.Error("dupe1Func.Call did not return error")
+ }
+ if task != nil {
+ t.Error("dupe1Func.Call posted a task")
+ }
+ if err := dupe2Func.Call(c.ctx); err != nil {
+ t.Errorf("dupe2Func.Call error: %v", err)
+ }
+ if task == nil {
+ t.Fatalf("dupe2Func.Call did not post a task")
+ }
+
+ // 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 dupeWhich == 1 {
+ t.Error("dupe2Func.Call used old registered function")
+ } else if dupeWhich != 2 {
+ t.Errorf("dupeWhich = %d; want 2", dupeWhich)
+ }
+}
+
+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)
+ }
+ task = tk
+ return tk, nil
+ }
+
+ reqFunc.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)
+
+ 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)
+ }
+}