| // Copyright 2016 Google LLC | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package controller | |
| import ( | |
| "bytes" | |
| "errors" | |
| "fmt" | |
| "strconv" | |
| "testing" | |
| "golang.org/x/net/context" | |
| "golang.org/x/oauth2" | |
| cd "google.golang.org/api/clouddebugger/v2" | |
| "google.golang.org/api/googleapi" | |
| ) | |
| const ( | |
| testDebuggeeID = "d12345" | |
| testBreakpointID = "bp12345" | |
| ) | |
| var ( | |
| // The sequence of wait tokens in List requests and responses. | |
| expectedWaitToken = []string{"init", "token1", "token2", "token1", "token1"} | |
| // The set of breakpoints returned from each List call. | |
| expectedBreakpoints = [][]*cd.Breakpoint{ | |
| nil, | |
| { | |
| &cd.Breakpoint{ | |
| Id: testBreakpointID, | |
| IsFinalState: false, | |
| Location: &cd.SourceLocation{Line: 42, Path: "foo.go"}, | |
| }, | |
| }, | |
| nil, | |
| } | |
| abortedError error = &googleapi.Error{ | |
| Code: 409, | |
| Message: "Conflict", | |
| Body: `{ | |
| "error": { | |
| "errors": [ | |
| { | |
| "domain": "global", | |
| "reason": "aborted", | |
| "message": "Conflict" | |
| } | |
| ], | |
| "code": 409, | |
| "message": "Conflict" | |
| } | |
| }`, | |
| Errors: []googleapi.ErrorItem{ | |
| {Reason: "aborted", Message: "Conflict"}, | |
| }, | |
| } | |
| backendError error = &googleapi.Error{ | |
| Code: 503, | |
| Message: "Backend Error", | |
| Body: `{ | |
| "error": { | |
| "errors": [ | |
| { | |
| "domain": "global", | |
| "reason": "backendError", | |
| "message": "Backend Error" | |
| } | |
| ], | |
| "code": 503, | |
| "message": "Backend Error" | |
| } | |
| }`, | |
| Errors: []googleapi.ErrorItem{ | |
| {Reason: "backendError", Message: "Backend Error"}, | |
| }, | |
| } | |
| ) | |
| type mockService struct { | |
| t *testing.T | |
| listCallsSeen int | |
| registerCallsSeen int | |
| } | |
| func (s *mockService) Register(ctx context.Context, req *cd.RegisterDebuggeeRequest) (*cd.RegisterDebuggeeResponse, error) { | |
| s.registerCallsSeen++ | |
| if req.Debuggee == nil { | |
| s.t.Errorf("missing debuggee") | |
| return nil, nil | |
| } | |
| if req.Debuggee.AgentVersion == "" { | |
| s.t.Errorf("missing agent version") | |
| } | |
| if req.Debuggee.Description == "" { | |
| s.t.Errorf("missing debuglet description") | |
| } | |
| if req.Debuggee.Project == "" { | |
| s.t.Errorf("missing project id") | |
| } | |
| if req.Debuggee.Uniquifier == "" { | |
| s.t.Errorf("missing uniquifier") | |
| } | |
| return &cd.RegisterDebuggeeResponse{ | |
| Debuggee: &cd.Debuggee{Id: testDebuggeeID}, | |
| }, nil | |
| } | |
| func (s *mockService) Update(ctx context.Context, id, breakpointID string, req *cd.UpdateActiveBreakpointRequest) (*cd.UpdateActiveBreakpointResponse, error) { | |
| if id != testDebuggeeID { | |
| s.t.Errorf("got debuggee ID %s want %s", id, testDebuggeeID) | |
| } | |
| if breakpointID != testBreakpointID { | |
| s.t.Errorf("got breakpoint ID %s want %s", breakpointID, testBreakpointID) | |
| } | |
| if !req.Breakpoint.IsFinalState { | |
| s.t.Errorf("got IsFinalState = false, want true") | |
| } | |
| return nil, nil | |
| } | |
| func (s *mockService) List(ctx context.Context, id, waitToken string) (*cd.ListActiveBreakpointsResponse, error) { | |
| if id != testDebuggeeID { | |
| s.t.Errorf("got debuggee ID %s want %s", id, testDebuggeeID) | |
| } | |
| if waitToken != expectedWaitToken[s.listCallsSeen] { | |
| s.t.Errorf("got wait token %s want %s", waitToken, expectedWaitToken[s.listCallsSeen]) | |
| } | |
| s.listCallsSeen++ | |
| if s.listCallsSeen == 4 { | |
| return nil, backendError | |
| } | |
| if s.listCallsSeen == 5 { | |
| return nil, abortedError | |
| } | |
| resp := &cd.ListActiveBreakpointsResponse{ | |
| Breakpoints: expectedBreakpoints[s.listCallsSeen-1], | |
| NextWaitToken: expectedWaitToken[s.listCallsSeen], | |
| } | |
| return resp, nil | |
| } | |
| func TestDebugletControllerClientLibrary(t *testing.T) { | |
| var ( | |
| m *mockService | |
| c *Controller | |
| list *cd.ListActiveBreakpointsResponse | |
| err error | |
| ) | |
| m = &mockService{t: t} | |
| newService = func(context.Context, oauth2.TokenSource) (serviceInterface, error) { return m, nil } | |
| opts := Options{ | |
| ProjectNumber: "5", | |
| ProjectID: "p1", | |
| AppModule: "mod1", | |
| AppVersion: "v1", | |
| } | |
| ctx := context.Background() | |
| if c, err = NewController(ctx, opts); err != nil { | |
| t.Fatal("Initializing Controller client:", err) | |
| } | |
| if err := validateLabels(c, opts); err != nil { | |
| t.Fatalf("Invalid labels:\n%v", err) | |
| } | |
| if list, err = c.List(ctx); err != nil { | |
| t.Fatal("List:", err) | |
| } | |
| if m.registerCallsSeen != 1 { | |
| t.Errorf("saw %d Register calls, want 1", m.registerCallsSeen) | |
| } | |
| if list, err = c.List(ctx); err != nil { | |
| t.Fatal("List:", err) | |
| } | |
| if len(list.Breakpoints) != 1 { | |
| t.Fatalf("got %d breakpoints, want 1", len(list.Breakpoints)) | |
| } | |
| if err = c.Update(ctx, list.Breakpoints[0].Id, &cd.Breakpoint{Id: testBreakpointID, IsFinalState: true}); err != nil { | |
| t.Fatal("Update:", err) | |
| } | |
| if list, err = c.List(ctx); err != nil { | |
| t.Fatal("List:", err) | |
| } | |
| if m.registerCallsSeen != 1 { | |
| t.Errorf("saw %d Register calls, want 1", m.registerCallsSeen) | |
| } | |
| // The next List call produces an error that should cause a Register call. | |
| if list, err = c.List(ctx); err == nil { | |
| t.Fatal("List should have returned an error") | |
| } | |
| if m.registerCallsSeen != 2 { | |
| t.Errorf("saw %d Register calls, want 2", m.registerCallsSeen) | |
| } | |
| // The next List call produces an error that should not cause a Register call. | |
| if list, err = c.List(ctx); err == nil { | |
| t.Fatal("List should have returned an error") | |
| } | |
| if m.registerCallsSeen != 2 { | |
| t.Errorf("saw %d Register calls, want 2", m.registerCallsSeen) | |
| } | |
| if m.listCallsSeen != 5 { | |
| t.Errorf("saw %d list calls, want 5", m.listCallsSeen) | |
| } | |
| } | |
| func validateLabels(c *Controller, o Options) error { | |
| errMsg := new(bytes.Buffer) | |
| if m, ok := c.labels["module"]; ok { | |
| if m != o.AppModule { | |
| errMsg.WriteString(fmt.Sprintf("label module: want %s, got %s\n", o.AppModule, m)) | |
| } | |
| } else { | |
| errMsg.WriteString("Missing \"module\" label\n") | |
| } | |
| if v, ok := c.labels["version"]; ok { | |
| if v != o.AppVersion { | |
| errMsg.WriteString(fmt.Sprintf("label version: want %s, got %s\n", o.AppVersion, v)) | |
| } | |
| } else { | |
| errMsg.WriteString("Missing \"version\" label\n") | |
| } | |
| if mv, ok := c.labels["minorversion"]; ok { | |
| if _, err := strconv.Atoi(mv); err != nil { | |
| errMsg.WriteString(fmt.Sprintln("label minorversion: not a numeric string:", mv)) | |
| } | |
| } else { | |
| errMsg.WriteString("Missing \"minorversion\" label\n") | |
| } | |
| if errMsg.Len() != 0 { | |
| return errors.New(errMsg.String()) | |
| } | |
| return nil | |
| } | |
| func TestIsAbortedError(t *testing.T) { | |
| if !isAbortedError(abortedError) { | |
| t.Errorf("isAborted(%+v): got false, want true", abortedError) | |
| } | |
| if isAbortedError(backendError) { | |
| t.Errorf("isAborted(%+v): got true, want false", backendError) | |
| } | |
| } |