| // 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" |
| "context" |
| "errors" |
| "fmt" |
| "strconv" |
| "testing" |
| |
| "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) |
| } |
| } |