go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tq/testing.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tq 16 17 import ( 18 "context" 19 "fmt" 20 "net/http" 21 "strings" 22 23 taskspb "cloud.google.com/go/cloudtasks/apiv2/cloudtaskspb" 24 25 "go.chromium.org/luci/common/logging" 26 27 "go.chromium.org/luci/server/tq/tqtesting" 28 ) 29 30 // TestingContext creates a scheduler that executes tasks through the given 31 // dispatcher (or Default one if nil) and puts it into the context as Submitter, 32 // so AddTask calls eventually submit tasks into this scheduler. 33 // 34 // The end result is that tasks submitted using such context end up in the 35 // returned Scheduler (allowing them to be examined), and when the Scheduler 36 // delivers them, they result in calls to corresponding handlers registered in 37 // the Dispatcher. 38 func TestingContext(ctx context.Context, d *Dispatcher) (context.Context, *tqtesting.Scheduler) { 39 if d == nil { 40 d = &Default 41 } 42 sched := &tqtesting.Scheduler{Executor: &directExecutor{d}} 43 return UseSubmitter(ctx, sched), sched 44 } 45 46 // directExecutor implements tqtesting.Executor via handlePush. 47 type directExecutor struct { 48 d *Dispatcher 49 } 50 51 func (e *directExecutor) Execute(ctx context.Context, t *tqtesting.Task, done func(retry bool)) { 52 retry := false 53 defer func() { done(retry) }() 54 55 switch { 56 case t.Message != nil && t.Task != nil: 57 panic("Both Task and Message are set") // break tests loudly 58 case t.Message != nil: 59 // Execute PubSub message by marking it done immediately. 60 logging.Debugf(ctx, "server/tq: sent a Pubsub message") 61 return 62 } 63 64 var body []byte 65 var headers map[string]string 66 switch mt := t.Task.MessageType.(type) { 67 case *taskspb.Task_HttpRequest: 68 body = mt.HttpRequest.Body 69 headers = mt.HttpRequest.Headers 70 case *taskspb.Task_AppEngineHttpRequest: 71 body = mt.AppEngineHttpRequest.Body 72 headers = mt.AppEngineHttpRequest.Headers 73 default: 74 panic(fmt.Sprintf("Bad task, no payload: %q", t.Task)) 75 } 76 77 hdr := make(http.Header, len(headers)) 78 for k, v := range headers { 79 hdr.Set(k, v) 80 } 81 info := parseHeaders(hdr) 82 83 // The direct executor doesn't emulate X-CloudTasks-* headers. 84 info.ExecutionCount = t.Attempts - 1 85 if index := strings.LastIndex(t.Name, "/tasks/"); index > 0 { 86 info.TaskID = t.Name[index+len("/tasks/"):] 87 } 88 89 ctx = logging.SetField(ctx, fmt.Sprintf("TQ-%.8s", info.TaskID), info.ExecutionCount) 90 err := e.d.handlePush(ctx, body, info) 91 if err != nil { 92 if !quietOnError.In(err) { 93 logging.Errorf(ctx, "server/tq task error: %s", err) 94 } 95 retry = !Fatal.In(err) && !Ignore.In(err) 96 } 97 }