blob: a5a9e32278c2a3a0506b58739c18b4c45cffa8e2 [file] [log] [blame]
// 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)
}
}