Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update coverage for xcode #26

Merged
merged 2 commits into from
Jul 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/commands/install/homebrew/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func NewInstallToolsCmd(iostream *iostreams.IOStreams) *cobra.Command {
cs := iostream.ColorScheme()
var configFile string
var force bool
var nonInteractive bool

cmd := &cobra.Command{
Use: "tools",
Expand All @@ -40,6 +41,7 @@ func NewInstallToolsCmd(iostream *iostreams.IOStreams) *cobra.Command {
}
cmd.Flags().StringVarP(&configFile, "config", "c", "config.yaml", "Path to the configuration file")
cmd.Flags().BoolVarP(&force, "force", "f", false, "Force reinstall of casks")
cmd.Flags().BoolVar(&nonInteractive, "non-interactive", false, "Run in non-interactive mode")
return cmd
}

Expand Down
4 changes: 3 additions & 1 deletion pkg/commands/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,14 @@ func NewInstallCmd(iostream *iostreams.IOStreams) *cobra.Command {
fmt.Fprintf(iostream.ErrOut, "failed to set force flag: %s\n", err)
return err
}

}

fmt.Fprintln(iostream.Out, cs.GreenBold("Running all installation subcommands..."))
for _, subcmd := range cmd.Commands() {
fmt.Printf("Running installation for %s...\n", subcmd.Use)
if len(subcmd.Use) == 0 {
continue
}
subSpan, subCtx := tracer.StartSpanFromContext(ctx, "install_"+subcmd.Use)
subcmd.SetContext(subCtx)
if subcmd.Use == "tools" {
Expand Down
230 changes: 169 additions & 61 deletions pkg/commands/install/install_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package install

import (
"errors"
"os"
"path/filepath"
"testing"

"github.com/XiaoConstantine/mycli/pkg/iostreams"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
)

Expand All @@ -15,66 +19,170 @@ func TestNewInstallCmd(t *testing.T) {
assert.Equal(t, "install", cmd.Use)
assert.Equal(t, "Install software", cmd.Short)
})
}

// Helper function to replace subcommands with mocks.
func mockSubcommands(cmd *cobra.Command, outcomes map[string]error) {
for _, subcmd := range cmd.Commands() {
// Capture the subcommand name and the intended mock outcome
if outcome, ok := outcomes[subcmd.Use]; ok {
subcmd.RunE = func(cmd *cobra.Command, args []string) error {
return outcome // Return the mock outcome when the subcommand is run
}
}
}
}

func TestNewInstallCmd_NonInteractiveMode_Force(t *testing.T) {
ios, _, outBuf, errBuf := iostreams.Test()
cmd := NewInstallCmd(ios)

// Create a temporary directory
tempDir, err := os.MkdirTemp("", "test-config-install")
assert.NoError(t, err)
defer os.RemoveAll(tempDir) // Clean up

// Create a temporary config file
configContent := []byte(`
tools:
- name: example-tool
`)
configPath := filepath.Join(tempDir, "test-config.yaml")
err = os.WriteFile(configPath, configContent, 0644)
assert.NoError(t, err)

// Set flags to simulate non-interactive mode
cmd.SetArgs([]string{"--non-interactive", "--config", configPath, "--force"})

// Disable base commands
cmd.Root().CompletionOptions.DisableDefaultCmd = true
cmd.SetHelpCommand(&cobra.Command{Hidden: true})

// Map of subcommand uses to their desired mock outcomes
outcomes := map[string]error{
"homebrew": nil, // Mock homebrew command to succeed
"xcode": errors.New("xcode installation failed"), // Mock xcode command to fail
"tools": nil,
}

mockSubcommands(cmd, outcomes)

err = cmd.Execute()
assert.Error(t, err) // Expect an error because xcode installation is mocked to fail
assert.Contains(t, errBuf.String(), "xcode installation failed")
assert.Contains(t, outBuf.String(), "Running all installation subcommands...\n") // Check output for successful mock
}

func TestNewInstallCmd_FileNotExist(t *testing.T) {
ios, _, _, errBuf := iostreams.Test()
cmd := NewInstallCmd(ios)

// Set flags to simulate file not found
cmd.SetArgs([]string{"--non-interactive", "--config", "/non/existent/path.yaml"})

err := cmd.Execute()
assert.Error(t, err)
assert.Contains(t, errBuf.String(), "Error: Config file does not exist at path")
}

func TestNewInstallCmdFlagsErrorsEdgeCases(t *testing.T) {
ios, _, outBuf, errBuf := iostreams.Test()
cmd := NewInstallCmd(ios)

// Create a temporary directory
tempDir, err := os.MkdirTemp("", "test-config-install")
assert.NoError(t, err)
defer os.RemoveAll(tempDir) // Clean up

// Create a temporary config file
configContent := []byte(`
tools:
- name: example-tool
`)
configPath := filepath.Join(tempDir, "test-config.yaml")
err = os.WriteFile(configPath, configContent, 0644)
assert.NoError(t, err)

// Setup mock outcomes with no specific subcommand behavior
// Map of subcommand uses to their desired mock outcomes
outcomes := map[string]error{
"homebrew": nil, // Mock homebrew command to succeed
"xcode": errors.New("xcode installation failed"), // Mock xcode command to fail
"tools": nil,
}

mockSubcommands(cmd, outcomes)

tests := []struct {
name string
args []string
setupFunc func()
cleanupFunc func()
expectErr bool
errorMsg string
successMsg string
}{
{
name: "Non-interactive with missing config file",
args: []string{"--non-interactive", "--config", "/fake/path.yaml"},
expectErr: true,
errorMsg: "Config file does not exist at path",
},
{
name: "Valid non-interactive force install",
args: []string{"--non-interactive", "--force", "--config", configPath},
setupFunc: func() {
// Mock environment variable and file path resolution
os.Setenv("HOME", "/tmp")
outcomes := map[string]error{
"homebrew": nil, // Mock homebrew command to succeed
"xcode": nil, // Mock xcode command to fail
"tools": nil,
}
mockSubcommands(cmd, outcomes)

},
cleanupFunc: func() {
os.Unsetenv("HOME")
mockSubcommands(cmd, map[string]error{})
},
expectErr: false,
successMsg: "All installations completed successfully.",
},
// {
// name: "Edge case with unexpected arguments",
// args: []string{"--non-interactive", "--unexpected_arg"},
// expectErr: true,
// errorMsg: "unknown flag: --unexpected_arg",
// },
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if tc.setupFunc != nil {
tc.setupFunc()
}
outBuf.Reset()
errBuf.Reset()

// Disable base commands
cmd.Root().CompletionOptions.DisableDefaultCmd = true
cmd.SetHelpCommand(&cobra.Command{Hidden: true})

cmd.SetArgs(tc.args)
err := cmd.Execute()

if tc.expectErr {
assert.Error(t, err)
assert.Contains(t, errBuf.String(), tc.errorMsg)
} else {
assert.NoError(t, err)
assert.Contains(t, outBuf.String(), tc.successMsg)
}

// t.Run("Non-interactive mode with valid config", func(t *testing.T) {
// ios, _, stdout, stderr := iostreams.Test()
// cmd := NewInstallCmd(ios)

// // Create a temporary directory
// tempDir, err := os.MkdirTemp("", "test-config")
// assert.NoError(t, err)
// defer os.RemoveAll(tempDir) // Clean up

// // Create a temporary config file
// configContent := []byte(`
// tools:
// - name: example-tool
// `)
// configPath := filepath.Join(tempDir, "config.yaml")
// err = os.WriteFile(configPath, configContent, 0644)
// assert.NoError(t, err)

// // Set flags
// cmd.Flags().Set("non-interactive", "true")
// cmd.Flags().Set("config", configPath)
// cmd.Flags().Set("force", "true")

// // Execute the command
// err = cmd.Execute()

// assert.NoError(t, err)
// assert.Contains(t, stdout.String(), "Running all installation subcommands...")
// assert.NotContains(t, stderr.String(), "Error:")
// })

// t.Run("Non-interactive mode with invalid config", func(t *testing.T) {
// ios, _, _, stderr := iostreams.Test()
// cmd := NewInstallCmd(ios)
// cmd.SetArgs([]string{"--non-interactive", "--config", "testdata/nonexistent_config.yaml"})
// err := cmd.Execute()
// assert.Error(t, err)
// assert.Contains(t, stderr.String(), "Error: Config file does not exist at path:")
// })

// t.Run("Interactive mode with Everything choice", func(t *testing.T) {
// ios, stdin, stdout, _ := iostreams.Test()
// cmd := NewInstallCmd(ios)
// stdin.WriteString("Everything\n")
// stdin.WriteString("testdata/valid_config.yaml\n")
// stdin.WriteString("n\n") // No force reinstall
// err := cmd.Execute()
// assert.NoError(t, err)
// assert.Contains(t, stdout.String(), "Running all installation subcommands...")
// })

// t.Run("Interactive mode with specific subcommand choice", func(t *testing.T) {
// ios, stdin, stdout, _ := iostreams.Test()
// cmd := NewInstallCmd(ios)
// stdin.WriteString("tools\n")
// stdin.WriteString("testdata/valid_config.yaml\n")
// stdin.WriteString("n\n") // No force reinstall
// err := cmd.Execute()
// assert.NoError(t, err)
// assert.Contains(t, stdout.String(), "Running installation for: tools...")
// })
if tc.cleanupFunc != nil {
tc.cleanupFunc()
}
})
}
}
2 changes: 1 addition & 1 deletion pkg/commands/install/xcode/xcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func NewCmdXcode(iostream *iostreams.IOStreams) *cobra.Command {

err := installCmd.Run()
if err != nil {
// fmt.Printf("Failed to install xcode: %v\n", err)
fmt.Fprintf(iostream.ErrOut, "Failed to install xcode: %v\n", err)
span.SetTag("error", true)
span.Finish(tracer.WithError(err))
return err
Expand Down
60 changes: 60 additions & 0 deletions pkg/commands/install/xcode/xcode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package xcode

import (
"context"
"errors"
"os/exec"
"testing"

Expand Down Expand Up @@ -78,3 +79,62 @@ func TestIsXcodeAlreadyInstalled(t *testing.T) {
})
}
}

func TestRunE(t *testing.T) {
oldExecCommandContext := execCommandContext
defer func() { execCommandContext = oldExecCommandContext }()

tests := []struct {
name string
mockInstalled bool
mockInstallErr error
expectError bool
}{
{
name: "Xcode already installed",
mockInstalled: true,
mockInstallErr: nil,
expectError: false,
},
{
name: "Xcode not installed, installation succeeds",
mockInstalled: false,
mockInstallErr: nil,
expectError: false,
},
{
name: "Xcode not installed, installation fails",
mockInstalled: false,
mockInstallErr: errors.New("installation failed"),
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
execCommandContext = func(ctx context.Context, command string, args ...string) *exec.Cmd {
if command == "xcode-select" && args[0] == "-p" {
if tt.mockInstalled {
return exec.Command("echo", "/Applications/Xcode.app/Contents/Developer")
}
return exec.Command("false")
}
if command == "xcode-select" && args[0] == "--install" {
if tt.mockInstallErr != nil {
return exec.Command("false")
}
return exec.Command("true")
}
return exec.Command("echo", "unexpected command")
}

ios, _, _, _ := iostreams.Test()
cmd := NewCmdXcode(ios)
err := cmd.RunE(cmd, []string{})

if (err != nil) != tt.expectError {
t.Errorf("Expected error: %v, got: %v", tt.expectError, err)
}
})
}
}
Loading