| package chromiumbuilder |
| |
| // Tests for code in service.go related to initial git checkout setup. This is |
| // kept separate from service_test.go due to the large amount of boilerplate |
| // code that is not relevant to other testing. |
| |
| import ( |
| "context" |
| "errors" |
| "fmt" |
| "os" |
| "path/filepath" |
| "testing" |
| |
| "github.com/stretchr/testify/require" |
| "go.skia.org/infra/go/exec" |
| "go.skia.org/infra/go/git" |
| "go.skia.org/infra/go/skerr" |
| vfs_mocks "go.skia.org/infra/go/vfs/mocks" |
| ) |
| |
| const ( |
| testDepotToolsPath = "/test/depot_tools" |
| testDepotToolsParentDir = "/test" |
| testChromiumPath = "/test/chromium/src" |
| testChromiumParentDir = "/test/chromium" |
| ) |
| |
| var validServiceArgs = fmt.Sprintf("%s=%s,%s=%s", ArgDepotToolsPath, testDepotToolsPath, ArgChromiumPath, testChromiumPath) |
| |
| func TestChromiumBuilderService_initImpl(t *testing.T) { |
| baseCtx := context.Background() |
| |
| tests := []struct { |
| name string |
| serviceArgs string |
| setupMocks func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) |
| expectError bool |
| errorMsgContains string |
| }{ |
| { |
| name: "happy path - both checkouts missing and successfully created", |
| serviceArgs: validServiceArgs, |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| // Depot Tools Mocks (missing checkout) - use baseCtx for mock matching |
| mockFS.On("Open", baseCtx, testDepotToolsPath).Return(nil, os.ErrNotExist).Once() |
| dc := func(path string, perm os.FileMode) error { |
| if path == testDepotToolsParentDir { |
| require.Equal(t, os.FileMode(0o750), perm) |
| return nil |
| } |
| if path == testChromiumParentDir { |
| require.Equal(t, os.FileMode(0o750), perm) |
| return nil |
| } |
| return errors.New("unexpected dc call") |
| } |
| mockDtCheckout := NewMockCheckout(t, testDepotToolsParentDir) |
| mockDtCheckout.On("Update", baseCtx).Return(nil).Once() |
| |
| // Chromium Mocks (missing checkout) |
| mockFS.On("Open", baseCtx, testChromiumPath).Return(nil, os.ErrNotExist).Once() |
| mockCrCheckout := NewMockCheckout(t, testChromiumParentDir) |
| // No Update call needed for chromium if fetched new |
| |
| cf := func(ctx context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| if repoUrl == DepotToolsUrl && workdir == testDepotToolsParentDir { |
| return mockDtCheckout, nil |
| } |
| if repoUrl == ChromiumUrl && workdir == testChromiumParentDir { |
| return mockCrCheckout, nil |
| } |
| return nil, errors.New("unexpected cf call") |
| } |
| ccr := func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- nil // Simulate successful fetch |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| return cf, dc, ccr |
| }, |
| expectError: false, |
| }, |
| { |
| name: "parseServiceArgs fails - missing depot_tools_path", |
| serviceArgs: fmt.Sprintf("%s=%s", ArgChromiumPath, testChromiumPath), |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| return nil, nil, nil // ccr not relevant here |
| }, |
| expectError: true, |
| errorMsgContains: "Did not receive a depot_tools_path argument", |
| }, |
| { |
| name: "handleDepotToolsSetup fails - dc fails for missing checkout", |
| serviceArgs: validServiceArgs, |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| mockFS.On("Open", baseCtx, testDepotToolsPath).Return(nil, os.ErrNotExist).Once() |
| dc := func(path string, perm os.FileMode) error { |
| if path == testDepotToolsParentDir { |
| return errors.New("dc failed for depot_tools") |
| } |
| return errors.New("unexpected dc call") |
| } |
| return nil, dc, nil // cf and ccr not reached |
| }, |
| expectError: true, |
| errorMsgContains: "dc failed for depot_tools", |
| }, |
| { |
| name: "handleChromiumSetup fails - dc fails for missing checkout", |
| serviceArgs: validServiceArgs, |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| // Depot Tools Mocks (missing checkout, success) |
| mockFS.On("Open", baseCtx, testDepotToolsPath).Return(nil, os.ErrNotExist).Once() |
| mockDtCheckout := NewMockCheckout(t, testDepotToolsParentDir) |
| mockDtCheckout.On("Update", baseCtx).Return(nil).Once() |
| |
| // Chromium Mocks (missing checkout, dc fails) |
| mockFS.On("Open", baseCtx, testChromiumPath).Return(nil, os.ErrNotExist).Once() |
| |
| dc := func(path string, perm os.FileMode) error { |
| if path == testDepotToolsParentDir { |
| return nil |
| } |
| if path == testChromiumParentDir { |
| return errors.New("dc failed for chromium") |
| } |
| return errors.New("unexpected dc call") |
| } |
| cf := func(ctx context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| if repoUrl == DepotToolsUrl && workdir == testDepotToolsParentDir { |
| return mockDtCheckout, nil |
| } |
| return nil, errors.New("unexpected cf call for chromium") |
| } |
| return cf, dc, nil // ccr not reached |
| }, |
| expectError: true, |
| errorMsgContains: "dc failed for chromium", |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := &ChromiumBuilderService{} |
| mockFS := vfs_mocks.NewFS(t) |
| mockCmdCollector := &exec.CommandCollector{} |
| |
| cf, dc, ccr := tt.setupMocks(t, mockFS, mockCmdCollector) |
| |
| err := s.initImpl(baseCtx, tt.serviceArgs, mockFS, cf, dc, ccr) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsgContains != "" { |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } |
| } else { |
| require.NoError(t, err) |
| require.Equal(t, testDepotToolsPath, s.depotToolsPath) |
| require.Equal(t, testChromiumPath, s.chromiumPath) |
| require.NotNil(t, s.depotToolsCheckout) |
| require.NotNil(t, s.chromiumCheckout) |
| if tt.name == "happy path - both checkouts missing and successfully created" { |
| commands := mockCmdCollector.Commands() |
| require.Len(t, commands, 1) |
| cmd := commands[0] |
| require.Equal(t, filepath.Join(testDepotToolsPath, "fetch"), cmd.Name) |
| require.Equal(t, []string{"--nohooks", "chromium"}, cmd.Args) |
| require.Equal(t, testChromiumParentDir, cmd.Dir) |
| require.NotNil(t, cmd.CombinedOutput) // As per fetchChromium |
| } |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_parseServiceArgs(t *testing.T) { |
| tests := []struct { |
| name string |
| serviceArgs string |
| expectError bool |
| errorMsgContains string |
| expectedDepotTools string |
| expectedChromium string |
| }{ |
| { |
| name: "happy path", |
| serviceArgs: fmt.Sprintf("%s=/dt,%s=/cr", ArgDepotToolsPath, ArgChromiumPath), |
| expectError: false, |
| expectedDepotTools: "/dt", |
| expectedChromium: "/cr", |
| }, |
| { |
| name: "happy path - different order", |
| serviceArgs: fmt.Sprintf("%s=/cr,%s=/dt", ArgChromiumPath, ArgDepotToolsPath), |
| expectError: false, |
| expectedDepotTools: "/dt", |
| expectedChromium: "/cr", |
| }, |
| { |
| name: "error - missing depot_tools_path", |
| serviceArgs: fmt.Sprintf("%s=/cr", ArgChromiumPath), |
| expectError: true, |
| errorMsgContains: "Did not receive a depot_tools_path argument", |
| }, |
| { |
| name: "error - missing chromium_path", |
| serviceArgs: fmt.Sprintf("%s=/dt", ArgDepotToolsPath), |
| expectError: true, |
| errorMsgContains: "Did not receive a chromium_path argument", |
| }, |
| { |
| name: "error - invalid format", |
| serviceArgs: "bad-arg", |
| expectError: true, |
| errorMsgContains: "is not in the expected key=value format", |
| }, |
| { |
| name: "error - unknown key", |
| serviceArgs: fmt.Sprintf("unknown_key=value,%s=/dt,%s=/cr", ArgDepotToolsPath, ArgChromiumPath), |
| expectError: true, |
| errorMsgContains: "Unknown argument key unknown_key", |
| }, |
| { |
| name: "error - empty string", |
| serviceArgs: "", |
| expectError: true, |
| errorMsgContains: "is not in the expected key=value format", |
| }, |
| { |
| name: "error - empty value for depot_tools_path", |
| serviceArgs: fmt.Sprintf("%s=,%s=/cr", ArgDepotToolsPath, ArgChromiumPath), |
| expectError: true, |
| errorMsgContains: "Did not receive a depot_tools_path argument", |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := &ChromiumBuilderService{} |
| err := s.parseServiceArgs(tt.serviceArgs) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsgContains != "" { |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } |
| } else { |
| require.NoError(t, err) |
| require.Equal(t, tt.expectedDepotTools, s.depotToolsPath) |
| require.Equal(t, tt.expectedChromium, s.chromiumPath) |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_handleDepotToolsSetup(t *testing.T) { |
| ctx := context.Background() |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{depotToolsPath: testDepotToolsPath} |
| } |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockFS *vfs_mocks.FS) (checkoutFactory, directoryCreator) |
| expectError bool |
| errorMsgContains string |
| expectDtCheckout bool |
| }{ |
| { |
| name: "happy path - existing checkout", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) (checkoutFactory, directoryCreator) { |
| // For the first Open in handleDepotToolsSetup for testDepotToolsPath |
| dtFileOpenedBySetup := vfs_mocks.NewFile(t) |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(dtFileOpenedBySetup, nil).Once() |
| dtFileOpenedBySetup.On("Close", ctx).Return(nil).Once() // Closed by defer in handleDepotToolsSetup |
| |
| // Mocks for handleExistingDepotToolsCheckout's success |
| // 1. checkIfPathIsDirectory(testDepotToolsPath) |
| dtFileOpenedByCheck := vfs_mocks.NewFile(t) |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(dtFileOpenedByCheck, nil).Once() // Second Open for testDepotToolsPath |
| dtFileInfo := &MockFileInfo{FIsDir: true} |
| dtFileOpenedByCheck.On("Stat", ctx).Return(dtFileInfo, nil).Once() |
| dtFileOpenedByCheck.On("Close", ctx).Return(nil).Once() |
| |
| // 2. Open lucicfg |
| lucicfgFile := vfs_mocks.NewFile(t) |
| mockFS.On("Open", ctx, filepath.Join(testDepotToolsPath, "lucicfg")).Return(lucicfgFile, nil).Once() |
| lucicfgFile.On("Close", ctx).Return(nil).Once() |
| |
| // 3. checkIfPathIsDirectory for .git |
| dotGitFile := vfs_mocks.NewFile(t) |
| mockFS.On("Open", ctx, filepath.Join(testDepotToolsPath, ".git")).Return(dotGitFile, nil).Once() |
| dotGitFileInfo := &MockFileInfo{FIsDir: true} |
| dotGitFile.On("Stat", ctx).Return(dotGitFileInfo, nil).Once() |
| dotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| mockCheckout := NewMockCheckout(t, testDepotToolsParentDir) |
| mockCheckout.On("Update", ctx).Return(nil).Once() |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| return cf, nil |
| }, |
| expectError: false, |
| expectDtCheckout: true, |
| }, |
| { |
| name: "happy path - missing checkout", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) (checkoutFactory, directoryCreator) { |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(nil, os.ErrNotExist).Once() |
| // Mocks for handleMissingDepotToolsCheckout's success |
| dc := func(path string, perm os.FileMode) error { return nil } |
| mockCheckout := NewMockCheckout(t, testDepotToolsParentDir) |
| mockCheckout.On("Update", ctx).Return(nil).Once() |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| return cf, dc |
| }, |
| expectError: false, |
| expectDtCheckout: true, |
| }, |
| { |
| name: "error - fs.Open fails with non-NotExist error", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) (checkoutFactory, directoryCreator) { |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(nil, errors.New("generic open error")).Once() |
| return nil, nil |
| }, |
| expectError: true, |
| errorMsgContains: "generic open error", |
| expectDtCheckout: false, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| mockFS := vfs_mocks.NewFS(t) |
| cf, dc := tt.setupMocks(t, mockFS) |
| |
| err := s.handleDepotToolsSetup(ctx, mockFS, cf, dc) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsgContains != "" { |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } |
| } else { |
| require.NoError(t, err) |
| } |
| if tt.expectDtCheckout { |
| require.NotNil(t, s.depotToolsCheckout) |
| } else { |
| require.Nil(t, s.depotToolsCheckout) |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_handleMissingDepotToolsCheckout(t *testing.T) { |
| ctx := context.Background() |
| const testDepotToolsPath = "/path/to/depot_tools" |
| const testDepotToolsParentDir = "/path/to" |
| |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{ |
| depotToolsPath: testDepotToolsPath, |
| } |
| } |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator) |
| expectError bool |
| expectedCheckout bool // whether s.depotToolsCheckout should be set |
| errorMsgContains string |
| }{ |
| { |
| name: "happy path", |
| setupMocks: func(t *testing.T, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator) { |
| dc := func(path string, perm os.FileMode) error { |
| require.Equal(t, testDepotToolsParentDir, path) |
| require.Equal(t, os.FileMode(0o750), perm) |
| return nil |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| require.Equal(t, DepotToolsUrl, repoUrl) |
| require.Equal(t, testDepotToolsParentDir, workdir) |
| return mockCheckout, nil |
| } |
| mockCheckout.On("Update", ctx).Return(nil).Once() |
| return cf, dc |
| }, |
| expectError: false, |
| expectedCheckout: true, |
| }, |
| { |
| name: "directory creator fails", |
| setupMocks: func(t *testing.T, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator) { |
| dc := func(path string, perm os.FileMode) error { |
| return errors.New("mkdir failed") |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("cf should not be called") // Should not be called |
| } |
| return cf, dc |
| }, |
| expectError: true, |
| expectedCheckout: false, |
| errorMsgContains: "mkdir failed", |
| }, |
| { |
| name: "checkout factory fails", |
| setupMocks: func(t *testing.T, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator) { |
| dc := func(path string, perm os.FileMode) error { |
| return nil |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("checkout factory failed") |
| } |
| return cf, dc |
| }, |
| expectError: true, |
| expectedCheckout: false, |
| errorMsgContains: "checkout factory failed", |
| }, |
| { |
| name: "checkout update fails", |
| setupMocks: func(t *testing.T, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator) { |
| dc := func(path string, perm os.FileMode) error { |
| return nil |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| mockCheckout.On("Update", ctx).Return(errors.New("update failed")).Once() |
| return cf, dc |
| }, |
| expectError: true, |
| expectedCheckout: true, // Checkout is assigned before update is called |
| errorMsgContains: "update failed", |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| mockFS := vfs_mocks.NewFS(t) // Not directly used by handleMissingDepotToolsCheckout |
| mockCheckout := NewMockCheckout(t, testDepotToolsParentDir) |
| |
| cf, dc := tt.setupMocks(t, mockCheckout) |
| |
| err := s.handleMissingDepotToolsCheckout(ctx, mockFS, cf, dc) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsgContains != "" { |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } |
| } else { |
| require.NoError(t, err) |
| } |
| |
| if tt.expectedCheckout { |
| require.NotNil(t, s.depotToolsCheckout) |
| if !tt.expectError { // If no error and checkout expected, it should be the mockCheckout |
| require.Equal(t, mockCheckout, s.depotToolsCheckout) |
| } |
| } else { |
| require.Nil(t, s.depotToolsCheckout) |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_handleExistingDepotToolsCheckout(t *testing.T) { |
| ctx := context.Background() |
| const testDepotToolsPath = "/path/to/depot_tools" |
| const testDepotToolsParentDir = "/path/to" |
| |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{ |
| depotToolsPath: testDepotToolsPath, |
| } |
| } |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory |
| expectError bool |
| errorMsg string |
| }{ |
| { |
| name: "happy path", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockDepotToolsFile := vfs_mocks.NewFile(t) |
| mockDepotToolsFileInfo := &MockFileInfo{FName: "depot_tools", FIsDir: true} |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(mockDepotToolsFile, nil).Once() |
| mockDepotToolsFile.On("Stat", ctx).Return(mockDepotToolsFileInfo, nil).Once() |
| mockDepotToolsFile.On("Close", ctx).Return(nil).Once() |
| |
| mockLucicfgFile := vfs_mocks.NewFile(t) |
| lucicfgPath := filepath.Join(testDepotToolsPath, "lucicfg") |
| mockFS.On("Open", ctx, lucicfgPath).Return(mockLucicfgFile, nil).Once() |
| mockLucicfgFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testDepotToolsPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: true} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| factory := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| require.Equal(t, DepotToolsUrl, repoUrl) |
| require.Equal(t, testDepotToolsParentDir, workdir) |
| return mockCheckout, nil |
| } |
| mockCheckout.On("Update", ctx).Return(nil).Once() |
| return factory |
| }, |
| }, |
| { |
| name: "depot_tools path is not a directory", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockDepotToolsFile := vfs_mocks.NewFile(t) |
| mockDepotToolsFileInfo := &MockFileInfo{FName: "depot_tools", FIsDir: false} |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(mockDepotToolsFile, nil).Once() |
| mockDepotToolsFile.On("Stat", ctx).Return(mockDepotToolsFileInfo, nil).Once() |
| mockDepotToolsFile.On("Close", ctx).Return(nil).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "Path /path/to/depot_tools exists, but is not a directory.", |
| }, |
| { |
| name: "depot_tools path open fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(nil, errors.New("open failed")).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "open failed", |
| }, |
| { |
| name: "lucicfg open fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockDepotToolsFile := vfs_mocks.NewFile(t) |
| mockDepotToolsFileInfo := &MockFileInfo{FName: "depot_tools", FIsDir: true} |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(mockDepotToolsFile, nil).Once() |
| mockDepotToolsFile.On("Stat", ctx).Return(mockDepotToolsFileInfo, nil).Once() |
| mockDepotToolsFile.On("Close", ctx).Return(nil).Once() |
| |
| lucicfgPath := filepath.Join(testDepotToolsPath, "lucicfg") |
| mockFS.On("Open", ctx, lucicfgPath).Return(nil, errors.New("lucicfg open failed")).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "lucicfg open failed", |
| }, |
| { |
| name: ".git path is not a directory", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockDepotToolsFile := vfs_mocks.NewFile(t) |
| mockDepotToolsFileInfo := &MockFileInfo{FName: "depot_tools", FIsDir: true} |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(mockDepotToolsFile, nil).Once() |
| mockDepotToolsFile.On("Stat", ctx).Return(mockDepotToolsFileInfo, nil).Once() |
| mockDepotToolsFile.On("Close", ctx).Return(nil).Once() |
| |
| mockLucicfgFile := vfs_mocks.NewFile(t) |
| lucicfgPath := filepath.Join(testDepotToolsPath, "lucicfg") |
| mockFS.On("Open", ctx, lucicfgPath).Return(mockLucicfgFile, nil).Once() |
| mockLucicfgFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testDepotToolsPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: false} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "Path /path/to/depot_tools/.git exists, but is not a directory.", |
| }, |
| { |
| name: "checkout factory fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockDepotToolsFile := vfs_mocks.NewFile(t) |
| mockDepotToolsFileInfo := &MockFileInfo{FName: "depot_tools", FIsDir: true} |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(mockDepotToolsFile, nil).Once() |
| mockDepotToolsFile.On("Stat", ctx).Return(mockDepotToolsFileInfo, nil).Once() |
| mockDepotToolsFile.On("Close", ctx).Return(nil).Once() |
| |
| mockLucicfgFile := vfs_mocks.NewFile(t) |
| lucicfgPath := filepath.Join(testDepotToolsPath, "lucicfg") |
| mockFS.On("Open", ctx, lucicfgPath).Return(mockLucicfgFile, nil).Once() |
| mockLucicfgFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testDepotToolsPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: true} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("factory failed") |
| } |
| }, |
| expectError: true, errorMsg: "factory failed", |
| }, |
| { |
| name: "checkout update fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockDepotToolsFile := vfs_mocks.NewFile(t) |
| mockDepotToolsFileInfo := &MockFileInfo{FName: "depot_tools", FIsDir: true} |
| mockFS.On("Open", ctx, testDepotToolsPath).Return(mockDepotToolsFile, nil).Once() |
| mockDepotToolsFile.On("Stat", ctx).Return(mockDepotToolsFileInfo, nil).Once() |
| mockDepotToolsFile.On("Close", ctx).Return(nil).Once() |
| |
| mockLucicfgFile := vfs_mocks.NewFile(t) |
| lucicfgPath := filepath.Join(testDepotToolsPath, "lucicfg") |
| mockFS.On("Open", ctx, lucicfgPath).Return(mockLucicfgFile, nil).Once() |
| mockLucicfgFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testDepotToolsPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: true} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| factory := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| mockCheckout.On("Update", ctx).Return(errors.New("update failed")).Once() |
| return factory |
| }, |
| expectError: true, errorMsg: "update failed", |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| mockFS := vfs_mocks.NewFS(t) |
| mockCheckout := NewMockCheckout(t, testDepotToolsParentDir) |
| |
| factory := tt.setupMocks(t, mockFS, mockCheckout) |
| |
| err := s.handleExistingDepotToolsCheckout(ctx, mockFS, factory) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsg != "" { |
| require.Contains(t, err.Error(), tt.errorMsg) |
| } |
| } else { |
| require.NoError(t, err) |
| require.NotNil(t, s.depotToolsCheckout) |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_handleChromiumSetup(t *testing.T) { |
| baseCtx := context.Background() // Base context |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{chromiumPath: testChromiumPath, depotToolsPath: testDepotToolsPath} |
| } |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) |
| expectError bool |
| errorMsgContains string |
| expectCrCheckout bool |
| expectedCmdsCount int |
| }{ |
| { |
| name: "happy path - existing checkout", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| // For the first Open in handleChromiumSetup for testChromiumPath - use baseCtx |
| crFileOpenedBySetup := vfs_mocks.NewFile(t) |
| mockFS.On("Open", baseCtx, testChromiumPath).Return(crFileOpenedBySetup, nil).Once() |
| crFileOpenedBySetup.On("Close", baseCtx).Return(nil).Once() // Closed by defer in handleChromiumSetup |
| |
| // Mocks for handleExistingChromiumCheckout's success |
| // 1. checkIfPathIsDirectory(testChromiumPath) |
| crFileOpenedByCheck := vfs_mocks.NewFile(t) |
| mockFS.On("Open", baseCtx, testChromiumPath).Return(crFileOpenedByCheck, nil).Once() // Second Open for testChromiumPath |
| crFileInfo := &MockFileInfo{FIsDir: true} |
| crFileOpenedByCheck.On("Stat", baseCtx).Return(crFileInfo, nil).Once() |
| crFileOpenedByCheck.On("Close", baseCtx).Return(nil).Once() |
| |
| // 2. checkIfPathIsDirectory for .git |
| dotGitFile := vfs_mocks.NewFile(t) |
| mockFS.On("Open", baseCtx, filepath.Join(testChromiumPath, ".git")).Return(dotGitFile, nil).Once() |
| dotGitFileInfo := &MockFileInfo{FIsDir: true} |
| dotGitFile.On("Stat", baseCtx).Return(dotGitFileInfo, nil).Once() |
| dotGitFile.On("Close", baseCtx).Return(nil).Once() |
| |
| mockCheckout := NewMockCheckout(t, testChromiumParentDir) |
| mockCheckout.On("Update", baseCtx).Return(nil).Once() |
| cf := func(ctx context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| return cf, nil, nil // ccr not used in existing checkout path |
| }, |
| expectError: false, |
| expectCrCheckout: true, |
| expectedCmdsCount: 0, |
| }, |
| { |
| name: "happy path - missing checkout", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| mockFS.On("Open", baseCtx, testChromiumPath).Return(nil, os.ErrNotExist).Once() |
| // Mocks for handleMissingChromiumCheckout's success |
| dc := func(path string, perm os.FileMode) error { return nil } |
| mockCheckout := NewMockCheckout(t, testChromiumParentDir) |
| // No Update call for new checkout |
| cf := func(ctx context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| ccr := func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- nil // Simulate successful fetch |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| return cf, dc, ccr |
| }, |
| expectError: false, |
| expectCrCheckout: true, |
| expectedCmdsCount: 1, // fetch command |
| }, |
| { |
| name: "error - fs.Open fails with non-NotExist error", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCmdCollector *exec.CommandCollector) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| mockFS.On("Open", baseCtx, testChromiumPath).Return(nil, errors.New("generic open error")).Once() |
| return nil, nil, nil // cf, dc, ccr not relevant |
| }, |
| expectError: true, |
| errorMsgContains: "generic open error", |
| expectCrCheckout: false, |
| expectedCmdsCount: 0, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| mockFS := vfs_mocks.NewFS(t) |
| mockCmdCollector := &exec.CommandCollector{} |
| |
| cf, dc, ccr := tt.setupMocks(t, mockFS, mockCmdCollector) |
| |
| err := s.handleChromiumSetup(baseCtx, mockFS, cf, dc, ccr) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsgContains != "" { |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } |
| } else { |
| require.NoError(t, err) |
| } |
| |
| if tt.expectCrCheckout { |
| require.NotNil(t, s.chromiumCheckout) |
| } else { |
| require.Nil(t, s.chromiumCheckout) |
| } |
| require.Len(t, mockCmdCollector.Commands(), tt.expectedCmdsCount) |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_handleMissingChromiumCheckout(t *testing.T) { |
| const testChromiumPath = "/path/to/chromium/src" |
| const testChromiumParentDir = "/path/to/chromium" |
| const testDepotToolsPath = "/fake/depot_tools" |
| |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{ |
| chromiumPath: testChromiumPath, |
| depotToolsPath: testDepotToolsPath, |
| } |
| } |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockCmdCollector *exec.CommandCollector, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator, concurrentCommandRunner) |
| expectError bool |
| expectedCheckout bool |
| errorMsgContains string |
| expectedCmdName string |
| expectedCmdArgs []string |
| expectedCmdDir string |
| }{ |
| { |
| name: "happy path", |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| dc := func(path string, perm os.FileMode) error { |
| require.Equal(t, testChromiumParentDir, path) |
| require.Equal(t, os.FileMode(0o750), perm) |
| return nil |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| require.Equal(t, ChromiumUrl, repoUrl) |
| require.Equal(t, testChromiumParentDir, workdir) |
| return mockCheckout, nil |
| } |
| ccr := func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- nil // Simulate successful fetch |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| return cf, dc, ccr |
| }, |
| expectError: false, |
| expectedCheckout: true, |
| expectedCmdName: filepath.Join(testDepotToolsPath, "fetch"), |
| expectedCmdArgs: []string{"--nohooks", "chromium"}, |
| expectedCmdDir: testChromiumParentDir, |
| }, |
| { |
| name: "directory creator fails", |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| dc := func(path string, perm os.FileMode) error { |
| return errors.New("mkdir failed") |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("cf should not be called") |
| } |
| ccr := func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- errors.New("ccr should not be called if dc fails") |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| return cf, dc, ccr |
| }, |
| expectError: true, |
| expectedCheckout: false, |
| errorMsgContains: "mkdir failed", |
| }, |
| { |
| name: "exec.Run fails (fetch command fails)", |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| dc := func(path string, perm os.FileMode) error { |
| return nil |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("cf should not be called") |
| } |
| ccr := func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- errors.New("fetch command failed") |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| return cf, dc, ccr |
| }, |
| expectError: true, |
| expectedCheckout: false, |
| errorMsgContains: "Failed to fetch Chromium", |
| expectedCmdName: filepath.Join(testDepotToolsPath, "fetch"), |
| expectedCmdArgs: []string{"--nohooks", "chromium"}, |
| expectedCmdDir: testChromiumParentDir, |
| }, |
| { |
| name: "checkout factory fails", |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector, mockCheckout *MockCheckout) (checkoutFactory, directoryCreator, concurrentCommandRunner) { |
| dc := func(path string, perm os.FileMode) error { |
| return nil |
| } |
| cf := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("checkout factory failed") |
| } |
| ccr := func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- nil // fetch is successful |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| |
| return cf, dc, ccr |
| }, |
| expectError: true, |
| expectedCheckout: false, |
| errorMsgContains: "checkout factory failed", |
| expectedCmdName: filepath.Join(testDepotToolsPath, "fetch"), |
| expectedCmdArgs: []string{"--nohooks", "chromium"}, |
| expectedCmdDir: testChromiumParentDir, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| mockFS := vfs_mocks.NewFS(t) |
| mockCmdCollector := &exec.CommandCollector{} |
| mockCheckout := NewMockCheckout(t, testChromiumParentDir) |
| |
| cf, dc, ccr := tt.setupMocks(t, mockCmdCollector, mockCheckout) |
| |
| err := s.handleMissingChromiumCheckout(context.Background(), mockFS, cf, dc, ccr) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsgContains != "" { |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } |
| } else { |
| require.NoError(t, err) |
| } |
| |
| if tt.expectedCheckout { |
| require.NotNil(t, s.chromiumCheckout) |
| if !tt.expectError { |
| require.Equal(t, mockCheckout, s.chromiumCheckout) |
| } |
| } else { |
| require.Nil(t, s.chromiumCheckout) |
| } |
| |
| commands := mockCmdCollector.Commands() |
| if tt.expectedCmdName != "" { |
| require.Len(t, commands, 1) |
| cmd := commands[0] |
| require.Equal(t, tt.expectedCmdName, cmd.Name) |
| require.Equal(t, tt.expectedCmdArgs, cmd.Args) |
| require.Equal(t, tt.expectedCmdDir, cmd.Dir) |
| require.NotNil(t, cmd.CombinedOutput) |
| } else { |
| require.Len(t, commands, 0) |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_handleExistingChromiumCheckout(t *testing.T) { |
| ctx := context.Background() |
| const testChromiumPath = "/path/to/chromium/src" |
| const testChromiumParentDir = "/path/to/chromium" |
| |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{ |
| chromiumPath: testChromiumPath, |
| } |
| } |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory |
| expectError bool |
| errorMsg string |
| }{ |
| { |
| name: "happy path", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockChromiumFile := vfs_mocks.NewFile(t) |
| mockChromiumFileInfo := &MockFileInfo{FName: "src", FIsDir: true} |
| mockFS.On("Open", ctx, testChromiumPath).Return(mockChromiumFile, nil).Once() |
| mockChromiumFile.On("Stat", ctx).Return(mockChromiumFileInfo, nil).Once() |
| mockChromiumFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testChromiumPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: true} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| factory := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| require.Equal(t, ChromiumUrl, repoUrl) |
| require.Equal(t, testChromiumParentDir, workdir) |
| return mockCheckout, nil |
| } |
| mockCheckout.On("Update", ctx).Return(nil).Once() |
| return factory |
| }, |
| }, |
| { |
| name: "chromium path is not a directory", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockChromiumFile := vfs_mocks.NewFile(t) |
| mockChromiumFileInfo := &MockFileInfo{FName: "src", FIsDir: false} |
| mockFS.On("Open", ctx, testChromiumPath).Return(mockChromiumFile, nil).Once() |
| mockChromiumFile.On("Stat", ctx).Return(mockChromiumFileInfo, nil).Once() |
| mockChromiumFile.On("Close", ctx).Return(nil).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "Path /path/to/chromium/src exists, but is not a directory.", |
| }, |
| { |
| name: "chromium path open fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockFS.On("Open", ctx, testChromiumPath).Return(nil, errors.New("open failed")).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "open failed", |
| }, |
| { |
| name: ".git path is not a directory", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockChromiumFile := vfs_mocks.NewFile(t) |
| mockChromiumFileInfo := &MockFileInfo{FName: "src", FIsDir: true} |
| mockFS.On("Open", ctx, testChromiumPath).Return(mockChromiumFile, nil).Once() |
| mockChromiumFile.On("Stat", ctx).Return(mockChromiumFileInfo, nil).Once() |
| mockChromiumFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testChromiumPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: false} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| }, |
| expectError: true, errorMsg: "Path /path/to/chromium/src/.git exists, but is not a directory.", |
| }, |
| { |
| name: "checkout factory fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockChromiumFile := vfs_mocks.NewFile(t) |
| mockChromiumFileInfo := &MockFileInfo{FName: "src", FIsDir: true} |
| mockFS.On("Open", ctx, testChromiumPath).Return(mockChromiumFile, nil).Once() |
| mockChromiumFile.On("Stat", ctx).Return(mockChromiumFileInfo, nil).Once() |
| mockChromiumFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testChromiumPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: true} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| return func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return nil, errors.New("factory failed") |
| } |
| }, |
| expectError: true, errorMsg: "factory failed", |
| }, |
| { |
| name: "checkout update fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS, mockCheckout *MockCheckout) checkoutFactory { |
| mockChromiumFile := vfs_mocks.NewFile(t) |
| mockChromiumFileInfo := &MockFileInfo{FName: "src", FIsDir: true} |
| mockFS.On("Open", ctx, testChromiumPath).Return(mockChromiumFile, nil).Once() |
| mockChromiumFile.On("Stat", ctx).Return(mockChromiumFileInfo, nil).Once() |
| mockChromiumFile.On("Close", ctx).Return(nil).Once() |
| |
| mockDotGitFile := vfs_mocks.NewFile(t) |
| dotGitPath := filepath.Join(testChromiumPath, ".git") |
| mockDotGitFileInfo := &MockFileInfo{FName: ".git", FIsDir: true} |
| mockFS.On("Open", ctx, dotGitPath).Return(mockDotGitFile, nil).Once() |
| mockDotGitFile.On("Stat", ctx).Return(mockDotGitFileInfo, nil).Once() |
| mockDotGitFile.On("Close", ctx).Return(nil).Once() |
| |
| factory := func(c context.Context, repoUrl string, workdir string) (git.Checkout, error) { |
| return mockCheckout, nil |
| } |
| mockCheckout.On("Update", ctx).Return(errors.New("update failed")).Once() |
| return factory |
| }, |
| expectError: true, errorMsg: "update failed", |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| mockFS := vfs_mocks.NewFS(t) |
| mockCheckout := NewMockCheckout(t, testChromiumParentDir) |
| |
| factory := tt.setupMocks(t, mockFS, mockCheckout) |
| |
| err := s.handleExistingChromiumCheckout(ctx, mockFS, factory) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| if tt.errorMsg != "" { |
| require.Contains(t, err.Error(), tt.errorMsg) |
| } |
| } else { |
| require.NoError(t, err) |
| require.NotNil(t, s.chromiumCheckout) |
| } |
| }) |
| } |
| } |
| |
| func TestChromiumBuilderService_fetchChromium(t *testing.T) { |
| const testDepotToolsPath = "/fake/depot_tools" |
| const testChromiumPath = "/fake/chromium/src" |
| const testChromiumParentDir = "/fake/chromium" |
| |
| setupService := func() *ChromiumBuilderService { |
| return &ChromiumBuilderService{ |
| depotToolsPath: testDepotToolsPath, |
| chromiumPath: testChromiumPath, |
| } |
| } |
| |
| tests := []struct { |
| name string |
| setupService func(s *ChromiumBuilderService) |
| setupMocks func(t *testing.T, mockCmdCollector *exec.CommandCollector) concurrentCommandRunner |
| expectError bool |
| errorMsgContains string |
| expectCmdRun bool |
| }{ |
| { |
| name: "happy path", |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector) concurrentCommandRunner { |
| return func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| // This is the core of the test. We check that fetchChromium calls runCancellableCommand, |
| // which in turn calls our mock ccr with the correct command. |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- nil // Simulate success |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| }, |
| expectError: false, |
| expectCmdRun: true, |
| }, |
| { |
| name: "fetch command fails", |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector) concurrentCommandRunner { |
| return func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| require.NoError(t, mockCmdCollector.Run(context.Background(), cmd)) |
| errCh := make(chan error, 1) |
| errCh <- errors.New("underlying fetch failed") |
| close(errCh) |
| mp := NewMockProcess(t) |
| return mp, errCh, nil |
| } |
| }, |
| expectError: true, |
| errorMsgContains: "Failed to fetch Chromium. Original error: underlying fetch failed", |
| expectCmdRun: true, |
| }, |
| { |
| name: "server shutting down", |
| setupService: func(s *ChromiumBuilderService) { |
| s.shuttingDown.Store(true) |
| }, |
| setupMocks: func(t *testing.T, mockCmdCollector *exec.CommandCollector) concurrentCommandRunner { |
| // ccr should not be called if server is shutting down. |
| return func(cmd *exec.Command) (exec.Process, <-chan error, error) { |
| t.Fatal("ccr should not have been called") |
| return nil, nil, nil |
| } |
| }, |
| expectError: true, |
| errorMsgContains: "Server is shutting down, not starting cancellable command.", |
| expectCmdRun: false, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| s := setupService() |
| if tt.setupService != nil { |
| tt.setupService(s) |
| } |
| mockCmdCollector := &exec.CommandCollector{} |
| ccr := tt.setupMocks(t, mockCmdCollector) |
| |
| err := s.fetchChromium(ccr) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } else { |
| require.NoError(t, err) |
| } |
| |
| commands := mockCmdCollector.Commands() |
| if tt.expectCmdRun { |
| require.Len(t, commands, 1) |
| cmd := commands[0] |
| require.Equal(t, filepath.Join(testDepotToolsPath, "fetch"), cmd.Name) |
| require.Equal(t, []string{"--nohooks", "chromium"}, cmd.Args) |
| require.Equal(t, testChromiumParentDir, cmd.Dir) |
| require.NotNil(t, cmd.CombinedOutput) |
| } else { |
| require.Len(t, commands, 0) |
| } |
| |
| // The lock should always be released. |
| require.True(t, s.chromiumFetchLock.TryLock(), "chromiumFetchLock should be unlocked after fetchChromium completes") |
| s.chromiumFetchLock.Unlock() |
| }) |
| } |
| } |
| |
| func TestCheckIfPathIsDirectory(t *testing.T) { |
| ctx := context.Background() |
| const testPath = "/fake/path/to/check" |
| |
| tests := []struct { |
| name string |
| setupMocks func(t *testing.T, mockFS *vfs_mocks.FS) |
| expectError bool |
| errorMsgContains string |
| }{ |
| { |
| name: "happy path - path is a directory", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) { |
| mockFile := vfs_mocks.NewFile(t) |
| mockFileInfo := &MockFileInfo{FIsDir: true} |
| mockFS.On("Open", ctx, testPath).Return(mockFile, nil).Once() |
| mockFile.On("Stat", ctx).Return(mockFileInfo, nil).Once() |
| mockFile.On("Close", ctx).Return(nil).Once() |
| }, |
| expectError: false, |
| }, |
| { |
| name: "error - path does not exist", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) { |
| mockFS.On("Open", ctx, testPath).Return(nil, os.ErrNotExist).Once() |
| }, |
| expectError: true, |
| errorMsgContains: os.ErrNotExist.Error(), |
| }, |
| { |
| name: "error - path is a file, not a directory", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) { |
| mockFile := vfs_mocks.NewFile(t) |
| mockFileInfo := &MockFileInfo{FIsDir: false} |
| mockFS.On("Open", ctx, testPath).Return(mockFile, nil).Once() |
| mockFile.On("Stat", ctx).Return(mockFileInfo, nil).Once() |
| mockFile.On("Close", ctx).Return(nil).Once() |
| }, |
| expectError: true, |
| errorMsgContains: "exists, but is not a directory", |
| }, |
| { |
| name: "error - stat fails", |
| setupMocks: func(t *testing.T, mockFS *vfs_mocks.FS) { |
| mockFile := vfs_mocks.NewFile(t) |
| mockFS.On("Open", ctx, testPath).Return(mockFile, nil).Once() |
| mockFile.On("Stat", ctx).Return(nil, errors.New("stat failed")).Once() |
| mockFile.On("Close", ctx).Return(nil).Once() |
| }, |
| expectError: true, |
| errorMsgContains: "stat failed", |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| mockFS := vfs_mocks.NewFS(t) |
| tt.setupMocks(t, mockFS) |
| |
| err := checkIfPathIsDirectory(ctx, mockFS, testPath) |
| |
| if tt.expectError { |
| require.Error(t, err) |
| require.Contains(t, err.Error(), tt.errorMsgContains) |
| } else { |
| require.NoError(t, err) |
| } |
| }) |
| } |
| } |
| |
| func TestIsNotExistWithUnwraps(t *testing.T) { |
| tests := []struct { |
| name string |
| err error |
| expected bool |
| }{ |
| { |
| name: "nil error", |
| err: nil, |
| expected: false, |
| }, |
| { |
| name: "os.ErrNotExist", |
| err: os.ErrNotExist, |
| expected: true, |
| }, |
| { |
| name: "different error", |
| err: errors.New("some other error"), |
| expected: false, |
| }, |
| { |
| name: "wrapped os.ErrNotExist", |
| err: fmt.Errorf("wrapped: %w", os.ErrNotExist), |
| expected: true, |
| }, |
| { |
| name: "wrapped different error", |
| err: fmt.Errorf("wrapped: %w", errors.New("some other error")), |
| expected: false, |
| }, |
| { |
| name: "deeply wrapped os.ErrNotExist", |
| err: fmt.Errorf("deeply: %w", fmt.Errorf("wrapped: %w", os.ErrNotExist)), |
| expected: true, |
| }, |
| { |
| name: "skerr wrapped os.ErrNotExist", |
| err: skerr.Wrap(os.ErrNotExist), |
| expected: true, |
| }, |
| } |
| |
| for _, tt := range tests { |
| t.Run(tt.name, func(t *testing.T) { |
| result := isNotExistWithUnwraps(tt.err) |
| require.Equal(t, tt.expected, result) |
| }) |
| } |
| } |