chore: polish ax v0.8.0 compliance
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
ba8b3df12c
commit
4021325d10
31 changed files with 398 additions and 279 deletions
|
|
@ -36,7 +36,7 @@ Three packages with a clear dependency direction: `devenv` -> `container` (root)
|
|||
## Coding Standards
|
||||
|
||||
- UK English (colour, organisation, honour)
|
||||
- Tests use testify; naming convention: `_Good` (happy path), `_Bad` (expected errors), `_Ugly` (edge cases)
|
||||
- Error wrapping: `fmt.Errorf("context: %w", err)`
|
||||
- Tests use testify; naming convention: `TestSubject_Function_{Good,Bad,Ugly}`
|
||||
- Error wrapping: `core.E("Op", "message", err)`
|
||||
- Context propagation: all blocking operations take `context.Context` as first parameter
|
||||
- Licence: EUPL-1.2
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ func showTemplateVars(name string) error {
|
|||
}
|
||||
|
||||
// RunFromTemplate builds and runs a LinuxKit image from a template.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := RunFromTemplate("core-dev", vars, runOpts)
|
||||
func RunFromTemplate(templateName string, vars map[string]string, runOpts container.RunOptions) error {
|
||||
// Apply template with variables
|
||||
content, err := container.ApplyTemplate(templateName, vars)
|
||||
|
|
@ -295,6 +299,10 @@ func lookupLinuxKit() (string, error) {
|
|||
|
||||
// ParseVarFlags parses --var flags into a map.
|
||||
// Format: --var KEY=VALUE or --var KEY="VALUE"
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// vars := ParseVarFlags([]string{"SSH_KEY=abc", "PORT=2222"})
|
||||
func ParseVarFlags(varFlags []string) map[string]string {
|
||||
vars := make(map[string]string)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ var (
|
|||
)
|
||||
|
||||
// AddVMCommands adds container-related commands under 'vm' to the CLI.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// AddVMCommands(root)
|
||||
func AddVMCommands(root *cli.Command) {
|
||||
vmCmd := &cli.Command{
|
||||
Use: "vm",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ type Manager interface {
|
|||
}
|
||||
|
||||
// GenerateID creates a new unique container ID (8 hex characters).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// id, err := GenerateID()
|
||||
func GenerateID() (string, error) {
|
||||
bytes := make([]byte, 4)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,14 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClaudeOptions_Default(t *testing.T) {
|
||||
func TestClaudeOptions_Default_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{}
|
||||
assert.False(t, opts.NoAuth)
|
||||
assert.Nil(t, opts.Auth)
|
||||
assert.Empty(t, opts.Model)
|
||||
}
|
||||
|
||||
func TestClaudeOptions_Custom(t *testing.T) {
|
||||
func TestClaudeOptions_Custom_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{
|
||||
NoAuth: true,
|
||||
Auth: []string{"gh", "anthropic"},
|
||||
|
|
@ -24,19 +24,19 @@ func TestClaudeOptions_Custom(t *testing.T) {
|
|||
assert.Equal(t, "opus", opts.Model)
|
||||
}
|
||||
|
||||
func TestFormatAuthList_Good_NoAuth(t *testing.T) {
|
||||
func TestFormatAuthList_NoAuth_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{NoAuth: true}
|
||||
result := formatAuthList(opts)
|
||||
assert.Equal(t, " (none)", result)
|
||||
}
|
||||
|
||||
func TestFormatAuthList_Good_Default(t *testing.T) {
|
||||
func TestFormatAuthList_Default_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{}
|
||||
result := formatAuthList(opts)
|
||||
assert.Equal(t, ", gh, anthropic, git", result)
|
||||
}
|
||||
|
||||
func TestFormatAuthList_Good_CustomAuth(t *testing.T) {
|
||||
func TestFormatAuthList_CustomAuth_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{
|
||||
Auth: []string{"gh"},
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ func TestFormatAuthList_Good_CustomAuth(t *testing.T) {
|
|||
assert.Equal(t, ", gh", result)
|
||||
}
|
||||
|
||||
func TestFormatAuthList_Good_MultipleAuth(t *testing.T) {
|
||||
func TestFormatAuthList_MultipleAuth_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{
|
||||
Auth: []string{"gh", "ssh", "git"},
|
||||
}
|
||||
|
|
@ -52,7 +52,7 @@ func TestFormatAuthList_Good_MultipleAuth(t *testing.T) {
|
|||
assert.Equal(t, ", gh, ssh, git", result)
|
||||
}
|
||||
|
||||
func TestFormatAuthList_Good_EmptyAuth(t *testing.T) {
|
||||
func TestFormatAuthList_EmptyAuth_Good(t *testing.T) {
|
||||
opts := ClaudeOptions{
|
||||
Auth: []string{},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@ type CDNConfig struct {
|
|||
}
|
||||
|
||||
// DefaultConfig returns sensible defaults.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cfg := DefaultConfig()
|
||||
func DefaultConfig() *Config {
|
||||
return &Config{
|
||||
Version: 1,
|
||||
|
|
@ -55,6 +59,10 @@ func DefaultConfig() *Config {
|
|||
}
|
||||
|
||||
// ConfigPath returns the path to the config file.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// path, err := ConfigPath()
|
||||
func ConfigPath() (string, error) {
|
||||
home := coreutil.HomeDir()
|
||||
if home == "" {
|
||||
|
|
@ -65,6 +73,10 @@ func ConfigPath() (string, error) {
|
|||
|
||||
// LoadConfig loads configuration from ~/.core/config.yaml using the provided medium.
|
||||
// Returns default config if file doesn't exist.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cfg, err := LoadConfig(io.Local)
|
||||
func LoadConfig(m io.Medium) (*Config, error) {
|
||||
configPath, err := ConfigPath()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -10,20 +10,20 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDefaultConfig(t *testing.T) {
|
||||
func TestConfig_DefaultConfig_Good(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
assert.Equal(t, 1, cfg.Version)
|
||||
assert.Equal(t, "auto", cfg.Images.Source)
|
||||
assert.Equal(t, "host-uk/core-images", cfg.Images.GitHub.Repo)
|
||||
}
|
||||
|
||||
func TestConfigPath(t *testing.T) {
|
||||
func TestConfig_ConfigPath_Good(t *testing.T) {
|
||||
path, err := ConfigPath()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, path, ".core/config.yaml")
|
||||
}
|
||||
|
||||
func TestLoadConfig_Good(t *testing.T) {
|
||||
func TestConfig_LoadConfig_Good(t *testing.T) {
|
||||
t.Run("returns default if not exists", func(t *testing.T) {
|
||||
// Mock HOME to a temp dir
|
||||
tempHome := t.TempDir()
|
||||
|
|
@ -60,7 +60,7 @@ images:
|
|||
})
|
||||
}
|
||||
|
||||
func TestLoadConfig_Bad(t *testing.T) {
|
||||
func TestConfig_LoadConfig_Bad(t *testing.T) {
|
||||
t.Run("invalid yaml", func(t *testing.T) {
|
||||
tempHome := t.TempDir()
|
||||
t.Setenv("HOME", tempHome)
|
||||
|
|
@ -77,7 +77,7 @@ func TestLoadConfig_Bad(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestConfig_Struct(t *testing.T) {
|
||||
func TestConfig_Struct_Good(t *testing.T) {
|
||||
cfg := &Config{
|
||||
Version: 2,
|
||||
Images: ImagesConfig{
|
||||
|
|
@ -100,7 +100,7 @@ func TestConfig_Struct(t *testing.T) {
|
|||
assert.Equal(t, "https://cdn.example.com", cfg.Images.CDN.URL)
|
||||
}
|
||||
|
||||
func TestDefaultConfig_Complete(t *testing.T) {
|
||||
func TestDefaultConfig_Complete_Good(t *testing.T) {
|
||||
cfg := DefaultConfig()
|
||||
assert.Equal(t, 1, cfg.Version)
|
||||
assert.Equal(t, "auto", cfg.Images.Source)
|
||||
|
|
@ -109,7 +109,7 @@ func TestDefaultConfig_Complete(t *testing.T) {
|
|||
assert.Empty(t, cfg.Images.CDN.URL)
|
||||
}
|
||||
|
||||
func TestLoadConfig_Good_PartialConfig(t *testing.T) {
|
||||
func TestLoadConfig_PartialConfig_Good(t *testing.T) {
|
||||
tempHome := t.TempDir()
|
||||
t.Setenv("HOME", tempHome)
|
||||
|
||||
|
|
@ -134,7 +134,7 @@ images:
|
|||
assert.Equal(t, "host-uk/core-images", cfg.Images.GitHub.Repo)
|
||||
}
|
||||
|
||||
func TestLoadConfig_Good_AllSourceTypes(t *testing.T) {
|
||||
func TestLoadConfig_AllSourceTypes_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
|
|
@ -203,7 +203,7 @@ images:
|
|||
}
|
||||
}
|
||||
|
||||
func TestImagesConfig_Struct(t *testing.T) {
|
||||
func TestImagesConfig_Struct_Good(t *testing.T) {
|
||||
ic := ImagesConfig{
|
||||
Source: "auto",
|
||||
GitHub: GitHubConfig{Repo: "test/repo"},
|
||||
|
|
@ -212,22 +212,22 @@ func TestImagesConfig_Struct(t *testing.T) {
|
|||
assert.Equal(t, "test/repo", ic.GitHub.Repo)
|
||||
}
|
||||
|
||||
func TestGitHubConfig_Struct(t *testing.T) {
|
||||
func TestGitHubConfig_Struct_Good(t *testing.T) {
|
||||
gc := GitHubConfig{Repo: "owner/repo"}
|
||||
assert.Equal(t, "owner/repo", gc.Repo)
|
||||
}
|
||||
|
||||
func TestRegistryConfig_Struct(t *testing.T) {
|
||||
func TestRegistryConfig_Struct_Good(t *testing.T) {
|
||||
rc := RegistryConfig{Image: "ghcr.io/owner/image:latest"}
|
||||
assert.Equal(t, "ghcr.io/owner/image:latest", rc.Image)
|
||||
}
|
||||
|
||||
func TestCDNConfig_Struct(t *testing.T) {
|
||||
func TestCDNConfig_Struct_Good(t *testing.T) {
|
||||
cc := CDNConfig{URL: "https://cdn.example.com/images"}
|
||||
assert.Equal(t, "https://cdn.example.com/images", cc.URL)
|
||||
}
|
||||
|
||||
func TestLoadConfig_Bad_UnreadableFile(t *testing.T) {
|
||||
func TestLoadConfig_UnreadableFile_Bad(t *testing.T) {
|
||||
// This test is platform-specific and may not work on all systems
|
||||
// Skip if we can't test file permissions properly
|
||||
if syscall.Getuid() == 0 {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@ type DevOps struct {
|
|||
}
|
||||
|
||||
// New creates a new DevOps instance using the provided medium.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// dev, err := New(io.Local)
|
||||
func New(m io.Medium) (*DevOps, error) {
|
||||
cfg, err := LoadConfig(m)
|
||||
if err != nil {
|
||||
|
|
@ -53,11 +57,19 @@ func New(m io.Medium) (*DevOps, error) {
|
|||
}
|
||||
|
||||
// ImageName returns the platform-specific image name.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// name := ImageName()
|
||||
func ImageName() string {
|
||||
return core.Sprintf("core-devops-%s-%s.qcow2", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
|
||||
// ImagesDir returns the path to the images directory.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// dir, err := ImagesDir()
|
||||
func ImagesDir() (string, error) {
|
||||
if dir := core.Env("CORE_IMAGES_DIR"); dir != "" {
|
||||
return dir, nil
|
||||
|
|
@ -70,6 +82,10 @@ func ImagesDir() (string, error) {
|
|||
}
|
||||
|
||||
// ImagePath returns the full path to the platform-specific image.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// path, err := ImagePath()
|
||||
func ImagePath() (string, error) {
|
||||
dir, err := ImagesDir()
|
||||
if err != nil {
|
||||
|
|
@ -106,6 +122,10 @@ type BootOptions struct {
|
|||
}
|
||||
|
||||
// DefaultBootOptions returns sensible defaults.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := DefaultBootOptions()
|
||||
func DefaultBootOptions() BootOptions {
|
||||
return BootOptions{
|
||||
Memory: 4096,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func newManagedTempDir(t *testing.T, prefix string) string {
|
|||
return dir
|
||||
}
|
||||
|
||||
func TestImageName(t *testing.T) {
|
||||
func TestDevOps_ImageName_Good(t *testing.T) {
|
||||
name := ImageName()
|
||||
assert.Contains(t, name, "core-devops-")
|
||||
assert.Contains(t, name, runtime.GOOS)
|
||||
|
|
@ -32,7 +32,7 @@ func TestImageName(t *testing.T) {
|
|||
assert.True(t, (name[len(name)-6:] == ".qcow2"))
|
||||
}
|
||||
|
||||
func TestImagesDir(t *testing.T) {
|
||||
func TestDevOps_ImagesDir_Good(t *testing.T) {
|
||||
t.Run("default directory", func(t *testing.T) {
|
||||
t.Setenv("CORE_IMAGES_DIR", "")
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ func TestImagesDir(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestImagePath(t *testing.T) {
|
||||
func TestDevOps_ImagePath_Good(t *testing.T) {
|
||||
customDir := "/tmp/images"
|
||||
t.Setenv("CORE_IMAGES_DIR", customDir)
|
||||
|
||||
|
|
@ -61,7 +61,7 @@ func TestImagePath(t *testing.T) {
|
|||
assert.Equal(t, expected, path)
|
||||
}
|
||||
|
||||
func TestDefaultBootOptions(t *testing.T) {
|
||||
func TestDevOps_DefaultBootOptions_Good(t *testing.T) {
|
||||
opts := DefaultBootOptions()
|
||||
assert.Equal(t, 4096, opts.Memory)
|
||||
assert.Equal(t, 2, opts.CPUs)
|
||||
|
|
@ -69,7 +69,7 @@ func TestDefaultBootOptions(t *testing.T) {
|
|||
assert.False(t, opts.Fresh)
|
||||
}
|
||||
|
||||
func TestIsInstalled_Bad(t *testing.T) {
|
||||
func TestDevOps_IsInstalled_Bad(t *testing.T) {
|
||||
t.Run("returns false for non-existent image", func(t *testing.T) {
|
||||
// Point to a temp directory that is empty
|
||||
tempDir := t.TempDir()
|
||||
|
|
@ -81,7 +81,7 @@ func TestIsInstalled_Bad(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestIsInstalled_Good(t *testing.T) {
|
||||
func TestDevOps_IsInstalled_Good(t *testing.T) {
|
||||
t.Run("returns true when image exists", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -145,7 +145,7 @@ func TestDevOps_Status_Good(t *testing.T) {
|
|||
assert.Equal(t, 4, status.CPUs)
|
||||
}
|
||||
|
||||
func TestDevOps_Status_Good_NotInstalled(t *testing.T) {
|
||||
func TestDevOps_Status_NotInstalled_Good(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -171,7 +171,7 @@ func TestDevOps_Status_Good_NotInstalled(t *testing.T) {
|
|||
assert.Equal(t, 2222, status.SSHPort)
|
||||
}
|
||||
|
||||
func TestDevOps_Status_Good_NoContainer(t *testing.T) {
|
||||
func TestDevOps_Status_NoContainer_Good(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -235,7 +235,7 @@ func TestDevOps_IsRunning_Good(t *testing.T) {
|
|||
assert.True(t, running)
|
||||
}
|
||||
|
||||
func TestDevOps_IsRunning_Bad_NotRunning(t *testing.T) {
|
||||
func TestDevOps_IsRunning_NotRunning_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -258,7 +258,7 @@ func TestDevOps_IsRunning_Bad_NotRunning(t *testing.T) {
|
|||
assert.False(t, running)
|
||||
}
|
||||
|
||||
func TestDevOps_IsRunning_Bad_ContainerStopped(t *testing.T) {
|
||||
func TestDevOps_IsRunning_ContainerStopped_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -326,7 +326,7 @@ func TestDevOps_findContainer_Good(t *testing.T) {
|
|||
assert.Equal(t, "my-container", found.Name)
|
||||
}
|
||||
|
||||
func TestDevOps_findContainer_Bad_NotFound(t *testing.T) {
|
||||
func TestDevOps_findContainer_NotFound_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -349,7 +349,7 @@ func TestDevOps_findContainer_Bad_NotFound(t *testing.T) {
|
|||
assert.Nil(t, found)
|
||||
}
|
||||
|
||||
func TestDevOps_Stop_Bad_NotFound(t *testing.T) {
|
||||
func TestDevOps_Stop_NotFound_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -372,7 +372,7 @@ func TestDevOps_Stop_Bad_NotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "not found")
|
||||
}
|
||||
|
||||
func TestBootOptions_Custom(t *testing.T) {
|
||||
func TestBootOptions_Custom_Good(t *testing.T) {
|
||||
opts := BootOptions{
|
||||
Memory: 8192,
|
||||
CPUs: 4,
|
||||
|
|
@ -385,7 +385,7 @@ func TestBootOptions_Custom(t *testing.T) {
|
|||
assert.True(t, opts.Fresh)
|
||||
}
|
||||
|
||||
func TestDevStatus_Struct(t *testing.T) {
|
||||
func TestDevStatus_Struct_Good(t *testing.T) {
|
||||
status := DevStatus{
|
||||
Installed: true,
|
||||
Running: true,
|
||||
|
|
@ -406,7 +406,7 @@ func TestDevStatus_Struct(t *testing.T) {
|
|||
assert.Equal(t, time.Hour, status.Uptime)
|
||||
}
|
||||
|
||||
func TestDevOps_Boot_Bad_NotInstalled(t *testing.T) {
|
||||
func TestDevOps_Boot_NotInstalled_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -429,7 +429,7 @@ func TestDevOps_Boot_Bad_NotInstalled(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "not installed")
|
||||
}
|
||||
|
||||
func TestDevOps_Boot_Bad_AlreadyRunning(t *testing.T) {
|
||||
func TestDevOps_Boot_AlreadyRunning_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -468,7 +468,7 @@ func TestDevOps_Boot_Bad_AlreadyRunning(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "already running")
|
||||
}
|
||||
|
||||
func TestDevOps_Status_Good_WithImageVersion(t *testing.T) {
|
||||
func TestDevOps_Status_WithImageVersion_Good(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -504,7 +504,7 @@ func TestDevOps_Status_Good_WithImageVersion(t *testing.T) {
|
|||
assert.Equal(t, "v1.2.3", status.ImageVersion)
|
||||
}
|
||||
|
||||
func TestDevOps_findContainer_Good_MultipleContainers(t *testing.T) {
|
||||
func TestDevOps_findContainer_MultipleContainers_Good(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -549,7 +549,7 @@ func TestDevOps_findContainer_Good_MultipleContainers(t *testing.T) {
|
|||
assert.Equal(t, "id-2", found.ID)
|
||||
}
|
||||
|
||||
func TestDevOps_Status_Good_ContainerWithUptime(t *testing.T) {
|
||||
func TestDevOps_Status_ContainerWithUptime_Good(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -586,7 +586,7 @@ func TestDevOps_Status_Good_ContainerWithUptime(t *testing.T) {
|
|||
assert.GreaterOrEqual(t, status.Uptime.Hours(), float64(1))
|
||||
}
|
||||
|
||||
func TestDevOps_IsRunning_Bad_DifferentContainerName(t *testing.T) {
|
||||
func TestDevOps_IsRunning_DifferentContainerName_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -621,7 +621,7 @@ func TestDevOps_IsRunning_Bad_DifferentContainerName(t *testing.T) {
|
|||
assert.False(t, running)
|
||||
}
|
||||
|
||||
func TestDevOps_Boot_Good_FreshFlag(t *testing.T) {
|
||||
func TestDevOps_Boot_FreshFlag_Good(t *testing.T) {
|
||||
t.Setenv("CORE_SKIP_SSH_SCAN", "true")
|
||||
tempDir := newManagedTempDir(t, "devops-test-")
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -669,7 +669,7 @@ func TestDevOps_Boot_Good_FreshFlag(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDevOps_Stop_Bad_ContainerNotRunning(t *testing.T) {
|
||||
func TestDevOps_Stop_ContainerNotRunning_Bad(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
@ -704,7 +704,7 @@ func TestDevOps_Stop_Bad_ContainerNotRunning(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "not running")
|
||||
}
|
||||
|
||||
func TestDevOps_Boot_Good_FreshWithNoExisting(t *testing.T) {
|
||||
func TestDevOps_Boot_FreshWithNoExisting_Good(t *testing.T) {
|
||||
t.Setenv("CORE_SKIP_SSH_SCAN", "true")
|
||||
tempDir := newManagedTempDir(t, "devops-boot-fresh-")
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -740,7 +740,7 @@ func TestDevOps_Boot_Good_FreshWithNoExisting(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestImageName_Format(t *testing.T) {
|
||||
func TestImageName_Format_Good(t *testing.T) {
|
||||
name := ImageName()
|
||||
// Check format: core-devops-{os}-{arch}.qcow2
|
||||
assert.Contains(t, name, "core-devops-")
|
||||
|
|
@ -749,7 +749,7 @@ func TestImageName_Format(t *testing.T) {
|
|||
assert.True(t, core.PathExt(name) == ".qcow2")
|
||||
}
|
||||
|
||||
func TestDevOps_Install_Delegates(t *testing.T) {
|
||||
func TestDevOps_Install_Delegates_Good(t *testing.T) {
|
||||
// This test verifies the Install method delegates to ImageManager
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -767,7 +767,7 @@ func TestDevOps_Install_Delegates(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDevOps_CheckUpdate_Delegates(t *testing.T) {
|
||||
func TestDevOps_CheckUpdate_Delegates_Good(t *testing.T) {
|
||||
// This test verifies the CheckUpdate method delegates to ImageManager
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -785,7 +785,7 @@ func TestDevOps_CheckUpdate_Delegates(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestDevOps_Boot_Good_Success(t *testing.T) {
|
||||
func TestDevOps_Boot_Success_Good(t *testing.T) {
|
||||
t.Setenv("CORE_SKIP_SSH_SCAN", "true")
|
||||
tempDir := newManagedTempDir(t, "devops-boot-success-")
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
|
@ -815,7 +815,7 @@ func TestDevOps_Boot_Good_Success(t *testing.T) {
|
|||
assert.NoError(t, err) // Mock hypervisor succeeds
|
||||
}
|
||||
|
||||
func TestDevOps_Config(t *testing.T) {
|
||||
func TestDevOps_Config_Good(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tempDir)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,10 @@ type ImageInfo struct {
|
|||
}
|
||||
|
||||
// NewImageManager creates a new image manager.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// manager, err := NewImageManager(io.Local, cfg)
|
||||
func NewImageManager(m io.Medium, cfg *Config) (*ImageManager, error) {
|
||||
imagesDir, err := ImagesDir()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestImageManager_Good_IsInstalled(t *testing.T) {
|
||||
func TestImageManager_IsInstalled_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -32,7 +32,7 @@ func TestImageManager_Good_IsInstalled(t *testing.T) {
|
|||
assert.True(t, mgr.IsInstalled())
|
||||
}
|
||||
|
||||
func TestNewImageManager_Good(t *testing.T) {
|
||||
func TestImages_NewImageManager_Good(t *testing.T) {
|
||||
t.Run("creates manager with cdn source", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
|
@ -62,7 +62,7 @@ func TestNewImageManager_Good(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestManifest_Save(t *testing.T) {
|
||||
func TestManifest_Save_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "manifest.json")
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ func TestManifest_Save(t *testing.T) {
|
|||
assert.Equal(t, "1.0.0", m2.Images["test.img"].Version)
|
||||
}
|
||||
|
||||
func TestLoadManifest_Bad(t *testing.T) {
|
||||
func TestImages_LoadManifest_Bad(t *testing.T) {
|
||||
t.Run("invalid json", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "manifest.json")
|
||||
|
|
@ -101,7 +101,7 @@ func TestLoadManifest_Bad(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestCheckUpdate_Bad(t *testing.T) {
|
||||
func TestImages_CheckUpdate_Bad(t *testing.T) {
|
||||
t.Run("image not installed", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
|
@ -116,7 +116,7 @@ func TestCheckUpdate_Bad(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestNewImageManager_Good_AutoSource(t *testing.T) {
|
||||
func TestNewImageManager_AutoSource_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ func TestNewImageManager_Good_AutoSource(t *testing.T) {
|
|||
assert.Len(t, mgr.sources, 2) // github and cdn
|
||||
}
|
||||
|
||||
func TestNewImageManager_Good_UnknownSourceFallsToAuto(t *testing.T) {
|
||||
func TestNewImageManager_UnknownSourceFallsToAuto_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -142,7 +142,7 @@ func TestNewImageManager_Good_UnknownSourceFallsToAuto(t *testing.T) {
|
|||
assert.Len(t, mgr.sources, 2) // falls to default (auto) which is github + cdn
|
||||
}
|
||||
|
||||
func TestLoadManifest_Good_Empty(t *testing.T) {
|
||||
func TestLoadManifest_Empty_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "nonexistent.json")
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ func TestLoadManifest_Good_Empty(t *testing.T) {
|
|||
assert.Equal(t, path, m.path)
|
||||
}
|
||||
|
||||
func TestLoadManifest_Good_ExistingData(t *testing.T) {
|
||||
func TestLoadManifest_ExistingData_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "manifest.json")
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ func TestLoadManifest_Good_ExistingData(t *testing.T) {
|
|||
assert.Equal(t, "cdn", m.Images["test.img"].Source)
|
||||
}
|
||||
|
||||
func TestImageInfo_Struct(t *testing.T) {
|
||||
func TestImageInfo_Struct_Good(t *testing.T) {
|
||||
info := ImageInfo{
|
||||
Version: "1.0.0",
|
||||
SHA256: "abc123",
|
||||
|
|
@ -182,7 +182,7 @@ func TestImageInfo_Struct(t *testing.T) {
|
|||
assert.Equal(t, "github", info.Source)
|
||||
}
|
||||
|
||||
func TestManifest_Save_Good_CreatesDirs(t *testing.T) {
|
||||
func TestManifest_Save_CreatesDirs_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
nestedPath := coreutil.JoinPath(tmpDir, "nested", "dir", "manifest.json")
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ func TestManifest_Save_Good_CreatesDirs(t *testing.T) {
|
|||
assert.True(t, io.Local.IsFile(nestedPath))
|
||||
}
|
||||
|
||||
func TestManifest_Save_Good_Overwrite(t *testing.T) {
|
||||
func TestManifest_Save_Overwrite_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "manifest.json")
|
||||
|
||||
|
|
@ -233,7 +233,7 @@ func TestManifest_Save_Good_Overwrite(t *testing.T) {
|
|||
assert.False(t, exists)
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Bad_NoSourceAvailable(t *testing.T) {
|
||||
func TestImageManager_Install_NoSourceAvailable_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -250,7 +250,7 @@ func TestImageManager_Install_Bad_NoSourceAvailable(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "no image source available")
|
||||
}
|
||||
|
||||
func TestNewImageManager_Good_CreatesDir(t *testing.T) {
|
||||
func TestNewImageManager_CreatesDir_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
imagesDir := coreutil.JoinPath(tmpDir, "images")
|
||||
t.Setenv("CORE_IMAGES_DIR", imagesDir)
|
||||
|
|
@ -289,7 +289,7 @@ func (m *mockImageSource) Download(ctx context.Context, medium io.Medium, dest s
|
|||
return medium.Write(imagePath, "mock image content")
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Good_WithMockSource(t *testing.T) {
|
||||
func TestImageManager_Install_WithMockSource_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -317,7 +317,7 @@ func TestImageManager_Install_Good_WithMockSource(t *testing.T) {
|
|||
assert.Equal(t, "mock", info.Source)
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Bad_DownloadError(t *testing.T) {
|
||||
func TestImageManager_Install_DownloadError_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -339,7 +339,7 @@ func TestImageManager_Install_Bad_DownloadError(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Bad_VersionError(t *testing.T) {
|
||||
func TestImageManager_Install_VersionError_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -361,7 +361,7 @@ func TestImageManager_Install_Bad_VersionError(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to get latest version")
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Good_SkipsUnavailableSource(t *testing.T) {
|
||||
func TestImageManager_Install_SkipsUnavailableSource_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ func TestImageManager_Install_Good_SkipsUnavailableSource(t *testing.T) {
|
|||
assert.Equal(t, "available", info.Source)
|
||||
}
|
||||
|
||||
func TestImageManager_CheckUpdate_Good_WithMockSource(t *testing.T) {
|
||||
func TestImageManager_CheckUpdate_WithMockSource_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -420,7 +420,7 @@ func TestImageManager_CheckUpdate_Good_WithMockSource(t *testing.T) {
|
|||
assert.True(t, hasUpdate)
|
||||
}
|
||||
|
||||
func TestImageManager_CheckUpdate_Good_NoUpdate(t *testing.T) {
|
||||
func TestImageManager_CheckUpdate_NoUpdate_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -450,7 +450,7 @@ func TestImageManager_CheckUpdate_Good_NoUpdate(t *testing.T) {
|
|||
assert.False(t, hasUpdate)
|
||||
}
|
||||
|
||||
func TestImageManager_CheckUpdate_Bad_NoSource(t *testing.T) {
|
||||
func TestImageManager_CheckUpdate_NoSource_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -477,7 +477,7 @@ func TestImageManager_CheckUpdate_Bad_NoSource(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "no image source available")
|
||||
}
|
||||
|
||||
func TestImageManager_CheckUpdate_Bad_VersionError(t *testing.T) {
|
||||
func TestImageManager_CheckUpdate_VersionError_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -505,7 +505,7 @@ func TestImageManager_CheckUpdate_Bad_VersionError(t *testing.T) {
|
|||
assert.Equal(t, "v1.0.0", current) // Current should still be returned
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Bad_EmptySources(t *testing.T) {
|
||||
func TestImageManager_Install_EmptySources_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -521,7 +521,7 @@ func TestImageManager_Install_Bad_EmptySources(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "no image source available")
|
||||
}
|
||||
|
||||
func TestImageManager_Install_Bad_AllUnavailable(t *testing.T) {
|
||||
func TestImageManager_Install_AllUnavailable_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -540,7 +540,7 @@ func TestImageManager_Install_Bad_AllUnavailable(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "no image source available")
|
||||
}
|
||||
|
||||
func TestImageManager_CheckUpdate_Good_FirstSourceUnavailable(t *testing.T) {
|
||||
func TestImageManager_CheckUpdate_FirstSourceUnavailable_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("CORE_IMAGES_DIR", tmpDir)
|
||||
|
||||
|
|
@ -567,7 +567,7 @@ func TestImageManager_CheckUpdate_Good_FirstSourceUnavailable(t *testing.T) {
|
|||
assert.True(t, hasUpdate)
|
||||
}
|
||||
|
||||
func TestManifest_Struct(t *testing.T) {
|
||||
func TestManifest_Struct_Good(t *testing.T) {
|
||||
m := &Manifest{
|
||||
Images: map[string]ImageInfo{
|
||||
"test.img": {Version: "1.0.0"},
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ func (d *DevOps) mountProject(ctx context.Context, path string) error {
|
|||
}
|
||||
|
||||
// DetectServeCommand auto-detects the serve command for a project.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cmd := DetectServeCommand(io.Local, ".")
|
||||
func DetectServeCommand(m io.Medium, projectDir string) string {
|
||||
// Laravel/Octane
|
||||
if hasFile(m, projectDir, "artisan") {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDetectServeCommand_Good_Laravel(t *testing.T) {
|
||||
func TestDetectServeCommand_Laravel_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "artisan"), "#!/usr/bin/env php")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -17,7 +17,7 @@ func TestDetectServeCommand_Good_Laravel(t *testing.T) {
|
|||
assert.Equal(t, "php artisan octane:start --host=0.0.0.0 --port=8000", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_NodeDev(t *testing.T) {
|
||||
func TestDetectServeCommand_NodeDev_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
packageJSON := `{"scripts":{"dev":"vite","start":"node index.js"}}`
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), packageJSON)
|
||||
|
|
@ -27,7 +27,7 @@ func TestDetectServeCommand_Good_NodeDev(t *testing.T) {
|
|||
assert.Equal(t, "npm run dev -- --host 0.0.0.0", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_NodeStart(t *testing.T) {
|
||||
func TestDetectServeCommand_NodeStart_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
packageJSON := `{"scripts":{"start":"node server.js"}}`
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), packageJSON)
|
||||
|
|
@ -37,7 +37,7 @@ func TestDetectServeCommand_Good_NodeStart(t *testing.T) {
|
|||
assert.Equal(t, "npm start", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_PHP(t *testing.T) {
|
||||
func TestDetectServeCommand_PHP_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `{"require":{}}`)
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -46,7 +46,7 @@ func TestDetectServeCommand_Good_PHP(t *testing.T) {
|
|||
assert.Equal(t, "frankenphp php-server -l :8000", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_GoMain(t *testing.T) {
|
||||
func TestDetectServeCommand_GoMain_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "go.mod"), "module example")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -57,7 +57,7 @@ func TestDetectServeCommand_Good_GoMain(t *testing.T) {
|
|||
assert.Equal(t, "go run .", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_GoWithoutMain(t *testing.T) {
|
||||
func TestDetectServeCommand_GoWithoutMain_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "go.mod"), "module example")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -67,7 +67,7 @@ func TestDetectServeCommand_Good_GoWithoutMain(t *testing.T) {
|
|||
assert.Equal(t, "python3 -m http.server 8000", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_Django(t *testing.T) {
|
||||
func TestDetectServeCommand_Django_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "manage.py"), "#!/usr/bin/env python")
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -76,14 +76,14 @@ func TestDetectServeCommand_Good_Django(t *testing.T) {
|
|||
assert.Equal(t, "python manage.py runserver 0.0.0.0:8000", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_Fallback(t *testing.T) {
|
||||
func TestDetectServeCommand_Fallback_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
cmd := DetectServeCommand(io.Local, tmpDir)
|
||||
assert.Equal(t, "python3 -m http.server 8000", cmd)
|
||||
}
|
||||
|
||||
func TestDetectServeCommand_Good_Priority(t *testing.T) {
|
||||
func TestDetectServeCommand_Priority_Good(t *testing.T) {
|
||||
// Laravel (artisan) should take priority over PHP (composer.json)
|
||||
tmpDir := t.TempDir()
|
||||
err := io.Local.Write(coreutil.JoinPath(tmpDir, "artisan"), "#!/usr/bin/env php")
|
||||
|
|
@ -95,13 +95,13 @@ func TestDetectServeCommand_Good_Priority(t *testing.T) {
|
|||
assert.Equal(t, "php artisan octane:start --host=0.0.0.0 --port=8000", cmd)
|
||||
}
|
||||
|
||||
func TestServeOptions_Default(t *testing.T) {
|
||||
func TestServeOptions_Default_Good(t *testing.T) {
|
||||
opts := ServeOptions{}
|
||||
assert.Equal(t, 0, opts.Port)
|
||||
assert.Equal(t, "", opts.Path)
|
||||
}
|
||||
|
||||
func TestServeOptions_Custom(t *testing.T) {
|
||||
func TestServeOptions_Custom_Good(t *testing.T) {
|
||||
opts := ServeOptions{
|
||||
Port: 3000,
|
||||
Path: "public",
|
||||
|
|
@ -110,7 +110,7 @@ func TestServeOptions_Custom(t *testing.T) {
|
|||
assert.Equal(t, "public", opts.Path)
|
||||
}
|
||||
|
||||
func TestHasFile_Good(t *testing.T) {
|
||||
func TestServe_HasFile_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
testFile := coreutil.JoinPath(tmpDir, "test.txt")
|
||||
err := io.Local.Write(testFile, "content")
|
||||
|
|
@ -119,13 +119,13 @@ func TestHasFile_Good(t *testing.T) {
|
|||
assert.True(t, hasFile(io.Local, tmpDir, "test.txt"))
|
||||
}
|
||||
|
||||
func TestHasFile_Bad(t *testing.T) {
|
||||
func TestServe_HasFile_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
assert.False(t, hasFile(io.Local, tmpDir, "nonexistent.txt"))
|
||||
}
|
||||
|
||||
func TestHasFile_Bad_Directory(t *testing.T) {
|
||||
func TestHasFile_Directory_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
subDir := coreutil.JoinPath(tmpDir, "subdir")
|
||||
err := io.Local.EnsureDir(subDir)
|
||||
|
|
|
|||
|
|
@ -6,13 +6,13 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShellOptions_Default(t *testing.T) {
|
||||
func TestShellOptions_Default_Good(t *testing.T) {
|
||||
opts := ShellOptions{}
|
||||
assert.False(t, opts.Console)
|
||||
assert.Nil(t, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_Console(t *testing.T) {
|
||||
func TestShellOptions_Console_Good(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Console: true,
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ func TestShellOptions_Console(t *testing.T) {
|
|||
assert.Nil(t, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_Command(t *testing.T) {
|
||||
func TestShellOptions_Command_Good(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Command: []string{"ls", "-la"},
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ func TestShellOptions_Command(t *testing.T) {
|
|||
assert.Equal(t, []string{"ls", "-la"}, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_ConsoleWithCommand(t *testing.T) {
|
||||
func TestShellOptions_ConsoleWithCommand_Good(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Console: true,
|
||||
Command: []string{"echo", "hello"},
|
||||
|
|
@ -37,7 +37,7 @@ func TestShellOptions_ConsoleWithCommand(t *testing.T) {
|
|||
assert.Equal(t, []string{"echo", "hello"}, opts.Command)
|
||||
}
|
||||
|
||||
func TestShellOptions_EmptyCommand(t *testing.T) {
|
||||
func TestShellOptions_EmptyCommand_Good(t *testing.T) {
|
||||
opts := ShellOptions{
|
||||
Command: []string{},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ func (d *DevOps) Test(ctx context.Context, projectDir string, opts TestOptions)
|
|||
}
|
||||
|
||||
// DetectTestCommand auto-detects the test command for a project.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cmd := DetectTestCommand(io.Local, ".")
|
||||
func DetectTestCommand(m io.Medium, projectDir string) string {
|
||||
// 1. Check .core/test.yaml
|
||||
cfg, err := LoadTestConfig(m, projectDir)
|
||||
|
|
@ -112,6 +116,10 @@ func DetectTestCommand(m io.Medium, projectDir string) string {
|
|||
}
|
||||
|
||||
// LoadTestConfig loads .core/test.yaml.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// cfg, err := LoadTestConfig(io.Local, ".")
|
||||
func LoadTestConfig(m io.Medium, projectDir string) (*TestConfig, error) {
|
||||
absPath := coreutil.AbsPath(coreutil.JoinPath(projectDir, ".core", "test.yaml"))
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
func TestDetectTestCommand_Good_ComposerJSON(t *testing.T) {
|
||||
func TestDetectTestCommand_ComposerJSON_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `{"scripts":{"test":"pest"}}`)
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ func TestDetectTestCommand_Good_ComposerJSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_PackageJSON(t *testing.T) {
|
||||
func TestDetectTestCommand_PackageJSON_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), `{"scripts":{"test":"vitest"}}`)
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ func TestDetectTestCommand_Good_PackageJSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_GoMod(t *testing.T) {
|
||||
func TestDetectTestCommand_GoMod_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "go.mod"), "module example")
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ func TestDetectTestCommand_Good_GoMod(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_CoreTestYaml(t *testing.T) {
|
||||
func TestDetectTestCommand_CoreTestYaml_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := coreutil.JoinPath(tmpDir, ".core")
|
||||
_ = io.Local.EnsureDir(coreDir)
|
||||
|
|
@ -49,7 +49,7 @@ func TestDetectTestCommand_Good_CoreTestYaml(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_Pytest(t *testing.T) {
|
||||
func TestDetectTestCommand_Pytest_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "pytest.ini"), "[pytest]")
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ func TestDetectTestCommand_Good_Pytest(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_Taskfile(t *testing.T) {
|
||||
func TestDetectTestCommand_Taskfile_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "Taskfile.yaml"), "version: '3'")
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ func TestDetectTestCommand_Good_Taskfile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Bad_NoFiles(t *testing.T) {
|
||||
func TestDetectTestCommand_NoFiles_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
cmd := DetectTestCommand(io.Local, tmpDir)
|
||||
|
|
@ -78,7 +78,7 @@ func TestDetectTestCommand_Bad_NoFiles(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_Priority(t *testing.T) {
|
||||
func TestDetectTestCommand_Priority_Good(t *testing.T) {
|
||||
// .core/test.yaml should take priority over other detection methods
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := coreutil.JoinPath(tmpDir, ".core")
|
||||
|
|
@ -92,7 +92,7 @@ func TestDetectTestCommand_Good_Priority(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadTestConfig_Good(t *testing.T) {
|
||||
func TestTest_LoadTestConfig_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := coreutil.JoinPath(tmpDir, ".core")
|
||||
_ = io.Local.EnsureDir(coreDir)
|
||||
|
|
@ -131,7 +131,7 @@ env:
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadTestConfig_Bad_NotFound(t *testing.T) {
|
||||
func TestLoadTestConfig_NotFound_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
_, err := LoadTestConfig(io.Local, tmpDir)
|
||||
|
|
@ -140,7 +140,7 @@ func TestLoadTestConfig_Bad_NotFound(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasPackageScript_Good(t *testing.T) {
|
||||
func TestTest_HasPackageScript_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), `{"scripts":{"test":"jest","build":"webpack"}}`)
|
||||
|
||||
|
|
@ -152,7 +152,7 @@ func TestHasPackageScript_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasPackageScript_Bad_MissingScript(t *testing.T) {
|
||||
func TestHasPackageScript_MissingScript_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), `{"scripts":{"build":"webpack"}}`)
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ func TestHasPackageScript_Bad_MissingScript(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasComposerScript_Good(t *testing.T) {
|
||||
func TestTest_HasComposerScript_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `{"scripts":{"test":"pest","post-install-cmd":"@php artisan migrate"}}`)
|
||||
|
||||
|
|
@ -170,7 +170,7 @@ func TestHasComposerScript_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasComposerScript_Bad_MissingScript(t *testing.T) {
|
||||
func TestHasComposerScript_MissingScript_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `{"scripts":{"build":"@php build.php"}}`)
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ func TestHasComposerScript_Bad_MissingScript(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTestConfig_Struct(t *testing.T) {
|
||||
func TestTestConfig_Struct_Good(t *testing.T) {
|
||||
cfg := &TestConfig{
|
||||
Version: 2,
|
||||
Command: "my-test",
|
||||
|
|
@ -200,7 +200,7 @@ func TestTestConfig_Struct(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTestCommand_Struct(t *testing.T) {
|
||||
func TestTestCommand_Struct_Good(t *testing.T) {
|
||||
cmd := TestCommand{
|
||||
Name: "integration",
|
||||
Run: "go test -tags=integration ./...",
|
||||
|
|
@ -213,7 +213,7 @@ func TestTestCommand_Struct(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTestOptions_Struct(t *testing.T) {
|
||||
func TestTestOptions_Struct_Good(t *testing.T) {
|
||||
opts := TestOptions{
|
||||
Name: "unit",
|
||||
Command: []string{"go", "test", "-v"},
|
||||
|
|
@ -226,7 +226,7 @@ func TestTestOptions_Struct(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_TaskfileYml(t *testing.T) {
|
||||
func TestDetectTestCommand_TaskfileYml_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "Taskfile.yml"), "version: '3'")
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ func TestDetectTestCommand_Good_TaskfileYml(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_Pyproject(t *testing.T) {
|
||||
func TestDetectTestCommand_Pyproject_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "pyproject.toml"), "[tool.pytest]")
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ func TestDetectTestCommand_Good_Pyproject(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasPackageScript_Bad_NoFile(t *testing.T) {
|
||||
func TestHasPackageScript_NoFile_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
if hasPackageScript(io.Local, tmpDir, "test") {
|
||||
|
|
@ -254,7 +254,7 @@ func TestHasPackageScript_Bad_NoFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasPackageScript_Bad_InvalidJSON(t *testing.T) {
|
||||
func TestHasPackageScript_InvalidJSON_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), `invalid json`)
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ func TestHasPackageScript_Bad_InvalidJSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasPackageScript_Bad_NoScripts(t *testing.T) {
|
||||
func TestHasPackageScript_NoScripts_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), `{"name":"test"}`)
|
||||
|
||||
|
|
@ -272,7 +272,7 @@ func TestHasPackageScript_Bad_NoScripts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasComposerScript_Bad_NoFile(t *testing.T) {
|
||||
func TestHasComposerScript_NoFile_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
if hasComposerScript(io.Local, tmpDir, "test") {
|
||||
|
|
@ -280,7 +280,7 @@ func TestHasComposerScript_Bad_NoFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasComposerScript_Bad_InvalidJSON(t *testing.T) {
|
||||
func TestHasComposerScript_InvalidJSON_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `invalid json`)
|
||||
|
||||
|
|
@ -289,7 +289,7 @@ func TestHasComposerScript_Bad_InvalidJSON(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHasComposerScript_Bad_NoScripts(t *testing.T) {
|
||||
func TestHasComposerScript_NoScripts_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `{"name":"test/pkg"}`)
|
||||
|
||||
|
|
@ -298,7 +298,7 @@ func TestHasComposerScript_Bad_NoScripts(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadTestConfig_Bad_InvalidYAML(t *testing.T) {
|
||||
func TestLoadTestConfig_InvalidYAML_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := coreutil.JoinPath(tmpDir, ".core")
|
||||
_ = io.Local.EnsureDir(coreDir)
|
||||
|
|
@ -310,7 +310,7 @@ func TestLoadTestConfig_Bad_InvalidYAML(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadTestConfig_Good_MinimalConfig(t *testing.T) {
|
||||
func TestLoadTestConfig_MinimalConfig_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
coreDir := coreutil.JoinPath(tmpDir, ".core")
|
||||
_ = io.Local.EnsureDir(coreDir)
|
||||
|
|
@ -328,7 +328,7 @@ func TestLoadTestConfig_Good_MinimalConfig(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_ComposerWithoutScript(t *testing.T) {
|
||||
func TestDetectTestCommand_ComposerWithoutScript_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
// composer.json without test script should not return composer test
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "composer.json"), `{"name":"test/pkg"}`)
|
||||
|
|
@ -340,7 +340,7 @@ func TestDetectTestCommand_Good_ComposerWithoutScript(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectTestCommand_Good_PackageJSONWithoutScript(t *testing.T) {
|
||||
func TestDetectTestCommand_PackageJSONWithoutScript_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
// package.json without test or dev script
|
||||
_ = io.Local.Write(coreutil.JoinPath(tmpDir, "package.json"), `{"name":"test"}`)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ Tests use `testify` for assertions. Most tests are self-contained and do not req
|
|||
|
||||
## Test naming convention
|
||||
|
||||
Tests follow a `_Good`, `_Bad`, `_Ugly` suffix pattern:
|
||||
Tests follow the `TestSubject_Function_{Good,Bad,Ugly}` pattern:
|
||||
|
||||
| Suffix | Meaning |
|
||||
|--------|---------|
|
||||
|
|
@ -51,9 +51,9 @@ Tests follow a `_Good`, `_Bad`, `_Ugly` suffix pattern:
|
|||
Examples from the codebase:
|
||||
|
||||
```go
|
||||
func TestNewState_Good(t *testing.T) { /* creates state successfully */ }
|
||||
func TestLoadState_Bad_InvalidJSON(t *testing.T) { /* handles corrupt state file */ }
|
||||
func TestGetHypervisor_Bad_Unknown(t *testing.T) { /* rejects unknown hypervisor name */ }
|
||||
func TestState_NewState_Good(t *testing.T) { /* creates state successfully */ }
|
||||
func TestLoadState_InvalidJSON_Bad(t *testing.T) { /* handles corrupt state file */ }
|
||||
func TestGetHypervisor_Unknown_Bad(t *testing.T) { /* rejects unknown hypervisor name */ }
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -108,7 +108,7 @@ go-container/
|
|||
|
||||
- **UK English** in all strings, comments, and documentation (colour, organisation, honour).
|
||||
- **Strict typing** -- All function parameters and return values are typed. No `interface{}` without justification.
|
||||
- **Error wrapping** -- Use `fmt.Errorf("context: %w", err)` for all error returns.
|
||||
- **Error wrapping** -- Use `core.E("Op", "message", err)` rather than `fmt.Errorf`.
|
||||
- **`io.Medium` abstraction** -- File system operations go through `io.Medium` (from `core/io`) rather than directly calling `os` functions. This enables testing with mock file systems. The `io.Local` singleton is used for real file system access.
|
||||
- **Compile-time interface checks** -- Use `var _ Interface = (*Impl)(nil)` to verify implementations at compile time (see `sources/cdn.go` and `sources/github.go`).
|
||||
- **Context propagation** -- All operations that might block accept a `context.Context` as their first parameter.
|
||||
|
|
@ -125,14 +125,14 @@ type MyHypervisor struct {
|
|||
|
||||
func (h *MyHypervisor) Name() string { return "my-hypervisor" }
|
||||
func (h *MyHypervisor) Available() bool { /* check if binary exists */ }
|
||||
func (h *MyHypervisor) BuildCommand(ctx context.Context, image string, opts *HypervisorOptions) (*exec.Cmd, error) {
|
||||
// Build and return exec.Cmd
|
||||
func (h *MyHypervisor) BuildCommand(ctx context.Context, image string, opts *HypervisorOptions) (*proc.Command, error) {
|
||||
// Build and return the command.
|
||||
}
|
||||
```
|
||||
|
||||
2. Register it in `DetectHypervisor()` and `GetHypervisor()` in `hypervisor.go`.
|
||||
|
||||
3. Add tests following the `_Good`/`_Bad` naming convention.
|
||||
3. Add tests following the `TestSubject_Function_{Good,Bad,Ugly}` naming convention.
|
||||
|
||||
|
||||
## Adding a new image source
|
||||
|
|
|
|||
33
go.sum
33
go.sum
|
|
@ -6,14 +6,12 @@ dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4=
|
|||
dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E=
|
||||
dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc=
|
||||
dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs=
|
||||
forge.lthn.ai/Snider/Borg v0.3.1/go.mod h1:Z7DJD0yHXsxSyM7Mjl6/g4gH1NBsIz44Bf5AFlV76Wg=
|
||||
forge.lthn.ai/core/cli v0.3.7 h1:1GrbaGg0wDGHr6+klSbbGyN/9sSbHvFbdySJznymhwg=
|
||||
forge.lthn.ai/core/cli v0.3.7/go.mod h1:DBUppJkA9P45ZFGgI2B8VXw1rAZxamHoI/KG7fRvTNs=
|
||||
forge.lthn.ai/core/config v0.1.8 h1:xP2hys7T94QGVF/OTh84/Zr5Dm/dL/0vzjht8zi+LOg=
|
||||
forge.lthn.ai/core/config v0.1.8/go.mod h1:8epZrkwoCt+5ayrqdinOUU/+w6UoxOyv9ZrdgVOgYfQ=
|
||||
forge.lthn.ai/core/go v0.3.3 h1:kYYZ2nRYy0/Be3cyuLJspRjLqTMxpckVyhb/7Sw2gd0=
|
||||
forge.lthn.ai/core/go v0.3.3/go.mod h1:Cp4ac25pghvO2iqOu59t1GyngTKVOzKB5/VPdhRi9CQ=
|
||||
forge.lthn.ai/core/go-crypt v0.1.6/go.mod h1:4VZAGqxlbadhSB66sJkdj54/HSJ+bSxVgwWK5kMMYDo=
|
||||
forge.lthn.ai/core/go-i18n v0.1.7 h1:aHkAoc3W8fw3RPNvw/UszQbjyFWXHszzbZgty3SwyAA=
|
||||
forge.lthn.ai/core/go-i18n v0.1.7/go.mod h1:0VDjwtY99NSj2iqwrI09h5GUsJeM9s48MLkr+/Dn4G8=
|
||||
forge.lthn.ai/core/go-inference v0.1.6 h1:ce42zC0zO8PuISUyAukAN1NACEdWp5wF1mRgnh5+58E=
|
||||
|
|
@ -22,22 +20,8 @@ forge.lthn.ai/core/go-io v0.1.7 h1:Tdb6sqh+zz1lsGJaNX9RFWM6MJ/RhSAyxfulLXrJsbk=
|
|||
forge.lthn.ai/core/go-io v0.1.7/go.mod h1:8lRLFk4Dnp5cR/Cyzh9WclD5566TbpdRgwcH7UZLWn4=
|
||||
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
|
||||
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
|
||||
github.com/ProtonMail/go-crypto v1.4.0/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.4/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.7/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.20/go.mod h1:oydPDJKcfMhgfcgBUZaG+toBbwy8yPWubJXBVERtI4o=
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.20/go.mod h1:YJ898MhD067hSHA6xYCx5ts/jEd8BSOLtQDL3iZsvbc=
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.21/go.mod h1:UUxgWxofmOdAMuqEsSppbDtGKLfR04HGsD0HXzvhI1k=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.12/go.mod h1:v2pNpJbRNl4vEUWEh5ytQok0zACAKfdmKS51Hotc3pQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.20/go.mod h1:V4X406Y666khGa8ghKmphma/7C0DAtEQYhkq9z4vpbk=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.20/go.mod h1:4TLZCmVJDM3FOu5P5TJP0zOlu9zWgDWU7aUxWbr+rcw=
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.1/go.mod h1:qXVal5H0ChqXP63t6jze5LmFalc7+ZE7wOdLtZ0LCP0=
|
||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
|
|
@ -48,19 +32,15 @@ github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF
|
|||
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
|
|
@ -71,7 +51,6 @@ github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPE
|
|||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
|
|
@ -92,12 +71,10 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU
|
|||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
|
|
@ -105,7 +82,6 @@ github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7
|
|||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
|
||||
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
|
|
@ -117,7 +93,6 @@ github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
|||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
|
|
@ -126,11 +101,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
|
|||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ=
|
||||
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
|
|
@ -139,13 +111,8 @@ golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
|||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.70.0/go.mod h1:OVmxFGP1CI/Z4L3E0Q3Mf1PDE0BucwMkcXjjLntvHJo=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.47.0/go.mod h1:hWjRO6Tj/5Ik8ieqxQybiEOUXy0NJFNp2tpvVpKlvig=
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@ type QemuHypervisor struct {
|
|||
}
|
||||
|
||||
// NewQemuHypervisor creates a new QEMU hypervisor instance.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// hv := NewQemuHypervisor()
|
||||
func NewQemuHypervisor() *QemuHypervisor {
|
||||
return &QemuHypervisor{
|
||||
Binary: "qemu-system-x86_64",
|
||||
|
|
@ -149,6 +153,10 @@ type HyperkitHypervisor struct {
|
|||
}
|
||||
|
||||
// NewHyperkitHypervisor creates a new Hyperkit hypervisor instance.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// hv := NewHyperkitHypervisor()
|
||||
func NewHyperkitHypervisor() *HyperkitHypervisor {
|
||||
return &HyperkitHypervisor{
|
||||
Binary: "hyperkit",
|
||||
|
|
@ -215,6 +223,10 @@ func (h *HyperkitHypervisor) BuildCommand(ctx context.Context, image string, opt
|
|||
}
|
||||
|
||||
// DetectImageFormat determines the image format from its file extension.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// format := DetectImageFormat("/tmp/core-dev.qcow2")
|
||||
func DetectImageFormat(path string) ImageFormat {
|
||||
ext := core.Lower(core.PathExt(path))
|
||||
switch ext {
|
||||
|
|
@ -232,6 +244,10 @@ func DetectImageFormat(path string) ImageFormat {
|
|||
}
|
||||
|
||||
// DetectHypervisor returns the best available hypervisor for the current platform.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// hv, err := DetectHypervisor()
|
||||
func DetectHypervisor() (Hypervisor, error) {
|
||||
// On macOS, prefer Hyperkit if available, fall back to QEMU
|
||||
if runtime.GOOS == "darwin" {
|
||||
|
|
@ -251,6 +267,10 @@ func DetectHypervisor() (Hypervisor, error) {
|
|||
}
|
||||
|
||||
// GetHypervisor returns a specific hypervisor by name.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// hv, err := GetHypervisor("qemu")
|
||||
func GetHypervisor(name string) (Hypervisor, error) {
|
||||
switch core.Lower(name) {
|
||||
case "qemu":
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func TestQemuHypervisor_Available_Good(t *testing.T) {
|
|||
assert.IsType(t, true, available)
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_Available_Bad_InvalidBinary(t *testing.T) {
|
||||
func TestQemuHypervisor_Available_InvalidBinary_Bad(t *testing.T) {
|
||||
q := &QemuHypervisor{
|
||||
Binary: "nonexistent-qemu-binary-that-does-not-exist",
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ func TestHyperkitHypervisor_Available_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Available_Bad_NotDarwin(t *testing.T) {
|
||||
func TestHyperkitHypervisor_Available_NotDarwin_Bad(t *testing.T) {
|
||||
if runtime.GOOS == "darwin" {
|
||||
t.Skip("This test only runs on non-darwin systems")
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ func TestHyperkitHypervisor_Available_Bad_NotDarwin(t *testing.T) {
|
|||
assert.False(t, available, "Hyperkit should not be available on non-darwin systems")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_Available_Bad_InvalidBinary(t *testing.T) {
|
||||
func TestHyperkitHypervisor_Available_InvalidBinary_Bad(t *testing.T) {
|
||||
h := &HyperkitHypervisor{
|
||||
Binary: "nonexistent-hyperkit-binary-that-does-not-exist",
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ func TestHyperkitHypervisor_Available_Bad_InvalidBinary(t *testing.T) {
|
|||
assert.False(t, available)
|
||||
}
|
||||
|
||||
func TestIsKVMAvailable_Good(t *testing.T) {
|
||||
func TestHypervisor_IsKVMAvailable_Good(t *testing.T) {
|
||||
// This test verifies the function runs without error
|
||||
// The actual result depends on the system
|
||||
result := isKVMAvailable()
|
||||
|
|
@ -80,7 +80,7 @@ func TestIsKVMAvailable_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectHypervisor_Good(t *testing.T) {
|
||||
func TestHypervisor_DetectHypervisor_Good(t *testing.T) {
|
||||
// DetectHypervisor tries to find an available hypervisor
|
||||
hv, err := DetectHypervisor()
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ func TestDetectHypervisor_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Good_Qemu(t *testing.T) {
|
||||
func TestGetHypervisor_Qemu_Good(t *testing.T) {
|
||||
hv, err := GetHypervisor("qemu")
|
||||
|
||||
// Depends on whether qemu is installed
|
||||
|
|
@ -107,7 +107,7 @@ func TestGetHypervisor_Good_Qemu(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Good_QemuUppercase(t *testing.T) {
|
||||
func TestGetHypervisor_QemuUppercase_Good(t *testing.T) {
|
||||
hv, err := GetHypervisor("QEMU")
|
||||
|
||||
// Depends on whether qemu is installed
|
||||
|
|
@ -119,7 +119,7 @@ func TestGetHypervisor_Good_QemuUppercase(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Good_Hyperkit(t *testing.T) {
|
||||
func TestGetHypervisor_Hyperkit_Good(t *testing.T) {
|
||||
hv, err := GetHypervisor("hyperkit")
|
||||
|
||||
// On non-darwin systems, should always fail
|
||||
|
|
@ -137,14 +137,14 @@ func TestGetHypervisor_Good_Hyperkit(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetHypervisor_Bad_Unknown(t *testing.T) {
|
||||
func TestGetHypervisor_Unknown_Bad(t *testing.T) {
|
||||
_, err := GetHypervisor("unknown-hypervisor")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown hypervisor")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_WithPortsAndVolumes(t *testing.T) {
|
||||
func TestQemuHypervisor_BuildCommand_WithPortsAndVolumes_Good(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -172,7 +172,7 @@ func TestQemuHypervisor_BuildCommand_Good_WithPortsAndVolumes(t *testing.T) {
|
|||
assert.Contains(t, args, "4")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_QCow2Format(t *testing.T) {
|
||||
func TestQemuHypervisor_BuildCommand_QCow2Format_Good(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -192,7 +192,7 @@ func TestQemuHypervisor_BuildCommand_Good_QCow2Format(t *testing.T) {
|
|||
assert.True(t, found, "Should have qcow2 drive argument")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_VMDKFormat(t *testing.T) {
|
||||
func TestQemuHypervisor_BuildCommand_VMDKFormat_Good(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -212,7 +212,7 @@ func TestQemuHypervisor_BuildCommand_Good_VMDKFormat(t *testing.T) {
|
|||
assert.True(t, found, "Should have vmdk drive argument")
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_RawFormat(t *testing.T) {
|
||||
func TestQemuHypervisor_BuildCommand_RawFormat_Good(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -232,7 +232,7 @@ func TestQemuHypervisor_BuildCommand_Good_RawFormat(t *testing.T) {
|
|||
assert.True(t, found, "Should have raw drive argument")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_WithPorts(t *testing.T) {
|
||||
func TestHyperkitHypervisor_BuildCommand_WithPorts_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -255,7 +255,7 @@ func TestHyperkitHypervisor_BuildCommand_Good_WithPorts(t *testing.T) {
|
|||
assert.Contains(t, args, "2")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_QCow2Format(t *testing.T) {
|
||||
func TestHyperkitHypervisor_BuildCommand_QCow2Format_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -266,7 +266,7 @@ func TestHyperkitHypervisor_BuildCommand_Good_QCow2Format(t *testing.T) {
|
|||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_RawFormat(t *testing.T) {
|
||||
func TestHyperkitHypervisor_BuildCommand_RawFormat_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -277,7 +277,7 @@ func TestHyperkitHypervisor_BuildCommand_Good_RawFormat(t *testing.T) {
|
|||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_NoPorts(t *testing.T) {
|
||||
func TestHyperkitHypervisor_BuildCommand_NoPorts_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -293,7 +293,7 @@ func TestHyperkitHypervisor_BuildCommand_Good_NoPorts(t *testing.T) {
|
|||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Good_NoSSHPort(t *testing.T) {
|
||||
func TestQemuHypervisor_BuildCommand_NoSSHPort_Good(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -309,7 +309,7 @@ func TestQemuHypervisor_BuildCommand_Good_NoSSHPort(t *testing.T) {
|
|||
assert.NotNil(t, cmd)
|
||||
}
|
||||
|
||||
func TestQemuHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
||||
func TestQemuHypervisor_BuildCommand_UnknownFormat_Bad(t *testing.T) {
|
||||
q := NewQemuHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -320,7 +320,7 @@ func TestQemuHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "unknown image format")
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Bad_UnknownFormat(t *testing.T) {
|
||||
func TestHyperkitHypervisor_BuildCommand_UnknownFormat_Bad(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -336,7 +336,7 @@ func TestHyperkitHypervisor_Name_Good(t *testing.T) {
|
|||
assert.Equal(t, "hyperkit", h.Name())
|
||||
}
|
||||
|
||||
func TestHyperkitHypervisor_BuildCommand_Good_ISOFormat(t *testing.T) {
|
||||
func TestHyperkitHypervisor_BuildCommand_ISOFormat_Good(t *testing.T) {
|
||||
h := NewHyperkitHypervisor()
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@ type LinuxKitManager struct {
|
|||
}
|
||||
|
||||
// NewLinuxKitManager creates a new LinuxKit manager with auto-detected hypervisor.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// manager, err := NewLinuxKitManager(io.Local)
|
||||
func NewLinuxKitManager(m io.Medium) (*LinuxKitManager, error) {
|
||||
statePath, err := DefaultStatePath()
|
||||
if err != nil {
|
||||
|
|
@ -46,6 +50,10 @@ func NewLinuxKitManager(m io.Medium) (*LinuxKitManager, error) {
|
|||
}
|
||||
|
||||
// NewLinuxKitManagerWithHypervisor creates a manager with a specific hypervisor.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// manager := NewLinuxKitManagerWithHypervisor(io.Local, state, hypervisor)
|
||||
func NewLinuxKitManagerWithHypervisor(m io.Medium, state *State, hypervisor Hypervisor) *LinuxKitManager {
|
||||
return &LinuxKitManager{
|
||||
state: state,
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ func newTestManager(t *testing.T) (*LinuxKitManager, *MockHypervisor, string) {
|
|||
return manager, mock, tmpDir
|
||||
}
|
||||
|
||||
func TestNewLinuxKitManagerWithHypervisor_Good(t *testing.T) {
|
||||
func TestLinuxKit_NewLinuxKitManagerWithHypervisor_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
statePath := coreutil.JoinPath(tmpDir, "containers.json")
|
||||
state, _ := LoadState(statePath)
|
||||
|
|
@ -87,7 +87,7 @@ func TestNewLinuxKitManagerWithHypervisor_Good(t *testing.T) {
|
|||
assert.Equal(t, mock, manager.Hypervisor())
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_Detached(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_Detached_Good(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
// Create a test image file
|
||||
|
|
@ -126,7 +126,7 @@ func TestLinuxKitManager_Run_Good_Detached(t *testing.T) {
|
|||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_DefaultValues(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_DefaultValues_Good(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := coreutil.JoinPath(tmpDir, "test.qcow2")
|
||||
|
|
@ -151,7 +151,7 @@ func TestLinuxKitManager_Run_Good_DefaultValues(t *testing.T) {
|
|||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_ImageNotFound(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_ImageNotFound_Bad(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -162,7 +162,7 @@ func TestLinuxKitManager_Run_Bad_ImageNotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "image not found")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_UnsupportedFormat(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_UnsupportedFormat_Bad(t *testing.T) {
|
||||
manager, _, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := coreutil.JoinPath(tmpDir, "test.txt")
|
||||
|
|
@ -202,7 +202,7 @@ func TestLinuxKitManager_Stop_Good(t *testing.T) {
|
|||
assert.Equal(t, StatusStopped, c.Status)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Stop_Bad_NotFound(t *testing.T) {
|
||||
func TestLinuxKitManager_Stop_NotFound_Bad(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -212,7 +212,7 @@ func TestLinuxKitManager_Stop_Bad_NotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "container not found")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Stop_Bad_NotRunning(t *testing.T) {
|
||||
func TestLinuxKitManager_Stop_NotRunning_Bad(t *testing.T) {
|
||||
_, _, tmpDir := newTestManager(t)
|
||||
statePath := coreutil.JoinPath(tmpDir, "containers.json")
|
||||
state, err := LoadState(statePath)
|
||||
|
|
@ -249,7 +249,7 @@ func TestLinuxKitManager_List_Good(t *testing.T) {
|
|||
assert.Len(t, containers, 2)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_List_Good_VerifiesRunningStatus(t *testing.T) {
|
||||
func TestLinuxKitManager_List_VerifiesRunningStatus_Good(t *testing.T) {
|
||||
_, _, tmpDir := newTestManager(t)
|
||||
statePath := coreutil.JoinPath(tmpDir, "containers.json")
|
||||
state, err := LoadState(statePath)
|
||||
|
|
@ -301,7 +301,7 @@ func TestLinuxKitManager_Logs_Good(t *testing.T) {
|
|||
assert.Equal(t, logContent, string(buf[:n]))
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Logs_Bad_NotFound(t *testing.T) {
|
||||
func TestLinuxKitManager_Logs_NotFound_Bad(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -311,7 +311,7 @@ func TestLinuxKitManager_Logs_Bad_NotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "container not found")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Logs_Bad_NoLogFile(t *testing.T) {
|
||||
func TestLinuxKitManager_Logs_NoLogFile_Bad(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
// Use a unique ID that won't have a log file
|
||||
|
|
@ -334,7 +334,7 @@ func TestLinuxKitManager_Logs_Bad_NoLogFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Exec_Bad_NotFound(t *testing.T) {
|
||||
func TestLinuxKitManager_Exec_NotFound_Bad(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
ctx := context.Background()
|
||||
|
|
@ -344,7 +344,7 @@ func TestLinuxKitManager_Exec_Bad_NotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "container not found")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Exec_Bad_NotRunning(t *testing.T) {
|
||||
func TestLinuxKitManager_Exec_NotRunning_Bad(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
container := &Container{ID: "abc12345", Status: StatusStopped}
|
||||
|
|
@ -357,7 +357,7 @@ func TestLinuxKitManager_Exec_Bad_NotRunning(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "not running")
|
||||
}
|
||||
|
||||
func TestDetectImageFormat_Good(t *testing.T) {
|
||||
func TestLinuxKit_DetectImageFormat_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
format ImageFormat
|
||||
|
|
@ -379,7 +379,7 @@ func TestDetectImageFormat_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDetectImageFormat_Bad_Unknown(t *testing.T) {
|
||||
func TestDetectImageFormat_Unknown_Bad(t *testing.T) {
|
||||
tests := []string{
|
||||
"/path/to/image.txt",
|
||||
"/path/to/image",
|
||||
|
|
@ -427,7 +427,7 @@ func TestQemuHypervisor_BuildCommand_Good(t *testing.T) {
|
|||
assert.Contains(t, args, "-nographic")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Logs_Good_Follow(t *testing.T) {
|
||||
func TestLinuxKitManager_Logs_Follow_Good(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
// Create a unique container ID
|
||||
|
|
@ -465,7 +465,7 @@ func TestLinuxKitManager_Logs_Good_Follow(t *testing.T) {
|
|||
assert.NoError(t, reader.Close())
|
||||
}
|
||||
|
||||
func TestFollowReader_Read_Good_WithData(t *testing.T) {
|
||||
func TestFollowReader_Read_WithData_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := coreutil.JoinPath(tmpDir, "test.log")
|
||||
|
||||
|
|
@ -498,7 +498,7 @@ func TestFollowReader_Read_Good_WithData(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestFollowReader_Read_Good_ContextCancel(t *testing.T) {
|
||||
func TestFollowReader_Read_ContextCancel_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := coreutil.JoinPath(tmpDir, "test.log")
|
||||
|
||||
|
|
@ -542,14 +542,14 @@ func TestFollowReader_Close_Good(t *testing.T) {
|
|||
assert.Error(t, readErr)
|
||||
}
|
||||
|
||||
func TestNewFollowReader_Bad_FileNotFound(t *testing.T) {
|
||||
func TestNewFollowReader_FileNotFound_Bad(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
_, err := newFollowReader(ctx, io.Local, "/nonexistent/path/to/file.log")
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_BuildCommandError(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_BuildCommandError_Bad(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
// Create a test image file
|
||||
|
|
@ -568,7 +568,7 @@ func TestLinuxKitManager_Run_Bad_BuildCommandError(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to build hypervisor command")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_Foreground(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_Foreground_Good(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
// Create a test image file
|
||||
|
|
@ -596,7 +596,7 @@ func TestLinuxKitManager_Run_Good_Foreground(t *testing.T) {
|
|||
assert.Equal(t, StatusStopped, container.Status)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Stop_Good_ContextCancelled(t *testing.T) {
|
||||
func TestLinuxKitManager_Stop_ContextCancelled_Good(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
// Create a test image file
|
||||
|
|
@ -633,19 +633,19 @@ func TestLinuxKitManager_Stop_Good_ContextCancelled(t *testing.T) {
|
|||
assert.Equal(t, context.Canceled, err)
|
||||
}
|
||||
|
||||
func TestIsProcessRunning_Good_ExistingProcess(t *testing.T) {
|
||||
func TestIsProcessRunning_ExistingProcess_Good(t *testing.T) {
|
||||
// Use our own PID which definitely exists
|
||||
running := isProcessRunning(syscall.Getpid())
|
||||
assert.True(t, running)
|
||||
}
|
||||
|
||||
func TestIsProcessRunning_Bad_NonexistentProcess(t *testing.T) {
|
||||
func TestIsProcessRunning_NonexistentProcess_Bad(t *testing.T) {
|
||||
// Use a PID that almost certainly doesn't exist
|
||||
running := isProcessRunning(999999)
|
||||
assert.False(t, running)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_WithPortsAndVolumes(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_WithPortsAndVolumes_Good(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := coreutil.JoinPath(tmpDir, "test.iso")
|
||||
|
|
@ -674,7 +674,7 @@ func TestLinuxKitManager_Run_Good_WithPortsAndVolumes(t *testing.T) {
|
|||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
|
||||
func TestFollowReader_Read_Bad_ReaderError(t *testing.T) {
|
||||
func TestFollowReader_Read_ReaderError_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
logPath := coreutil.JoinPath(tmpDir, "test.log")
|
||||
|
||||
|
|
@ -695,7 +695,7 @@ func TestFollowReader_Read_Bad_ReaderError(t *testing.T) {
|
|||
assert.Error(t, readErr)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_StartError(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_StartError_Bad(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := coreutil.JoinPath(tmpDir, "test.iso")
|
||||
|
|
@ -716,7 +716,7 @@ func TestLinuxKitManager_Run_Bad_StartError(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to start VM")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Bad_ForegroundStartError(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_ForegroundStartError_Bad(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := coreutil.JoinPath(tmpDir, "test.iso")
|
||||
|
|
@ -737,7 +737,7 @@ func TestLinuxKitManager_Run_Bad_ForegroundStartError(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "failed to start VM")
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Run_Good_ForegroundWithError(t *testing.T) {
|
||||
func TestLinuxKitManager_Run_ForegroundWithError_Good(t *testing.T) {
|
||||
manager, mock, tmpDir := newTestManager(t)
|
||||
|
||||
imagePath := coreutil.JoinPath(tmpDir, "test.iso")
|
||||
|
|
@ -760,7 +760,7 @@ func TestLinuxKitManager_Run_Good_ForegroundWithError(t *testing.T) {
|
|||
assert.Equal(t, StatusError, container.Status)
|
||||
}
|
||||
|
||||
func TestLinuxKitManager_Stop_Good_ProcessExitedWhileRunning(t *testing.T) {
|
||||
func TestLinuxKitManager_Stop_ProcessExitedWhileRunning_Good(t *testing.T) {
|
||||
manager, _, _ := newTestManager(t)
|
||||
|
||||
// Add a "running" container with a process that has already exited
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ type CDNSource struct {
|
|||
var _ ImageSource = (*CDNSource)(nil)
|
||||
|
||||
// NewCDNSource creates a new CDN source.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// src := NewCDNSource(cfg)
|
||||
func NewCDNSource(cfg SourceConfig) *CDNSource {
|
||||
return &CDNSource{config: cfg}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCDNSource_Good_Available(t *testing.T) {
|
||||
func TestCDNSource_Available_Good(t *testing.T) {
|
||||
src := NewCDNSource(SourceConfig{
|
||||
CDNURL: "https://images.example.com",
|
||||
ImageName: "core-devops-darwin-arm64.qcow2",
|
||||
|
|
@ -23,7 +23,7 @@ func TestCDNSource_Good_Available(t *testing.T) {
|
|||
assert.True(t, src.Available())
|
||||
}
|
||||
|
||||
func TestCDNSource_Bad_NoURL(t *testing.T) {
|
||||
func TestCDNSource_NoURL_Bad(t *testing.T) {
|
||||
src := NewCDNSource(SourceConfig{
|
||||
ImageName: "core-devops-darwin-arm64.qcow2",
|
||||
})
|
||||
|
|
@ -115,7 +115,7 @@ func TestCDNSource_Download_Bad(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestCDNSource_LatestVersion_Bad_NoManifest(t *testing.T) {
|
||||
func TestCDNSource_LatestVersion_NoManifest_Bad(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
|
|
@ -131,7 +131,7 @@ func TestCDNSource_LatestVersion_Bad_NoManifest(t *testing.T) {
|
|||
assert.Equal(t, "latest", version)
|
||||
}
|
||||
|
||||
func TestCDNSource_LatestVersion_Bad_ServerError(t *testing.T) {
|
||||
func TestCDNSource_LatestVersion_ServerError_Bad(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}))
|
||||
|
|
@ -147,7 +147,7 @@ func TestCDNSource_LatestVersion_Bad_ServerError(t *testing.T) {
|
|||
assert.Equal(t, "latest", version)
|
||||
}
|
||||
|
||||
func TestCDNSource_Download_Good_NoProgress(t *testing.T) {
|
||||
func TestCDNSource_Download_NoProgress_Good(t *testing.T) {
|
||||
content := "test content"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Length", core.Sprintf("%d", len(content)))
|
||||
|
|
@ -171,7 +171,7 @@ func TestCDNSource_Download_Good_NoProgress(t *testing.T) {
|
|||
assert.Equal(t, content, data)
|
||||
}
|
||||
|
||||
func TestCDNSource_Download_Good_LargeFile(t *testing.T) {
|
||||
func TestCDNSource_Download_LargeFile_Good(t *testing.T) {
|
||||
// Create content larger than buffer size (32KB)
|
||||
content := make([]byte, 64*1024) // 64KB
|
||||
for i := range content {
|
||||
|
|
@ -203,7 +203,7 @@ func TestCDNSource_Download_Good_LargeFile(t *testing.T) {
|
|||
assert.Equal(t, int64(len(content)), lastDownloaded)
|
||||
}
|
||||
|
||||
func TestCDNSource_Download_Bad_HTTPErrorCodes(t *testing.T) {
|
||||
func TestCDNSource_Download_HTTPErrorCodes_Bad(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
statusCode int
|
||||
|
|
@ -235,12 +235,12 @@ func TestCDNSource_Download_Bad_HTTPErrorCodes(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCDNSource_InterfaceCompliance(t *testing.T) {
|
||||
func TestCDNSource_InterfaceCompliance_Good(t *testing.T) {
|
||||
// Verify CDNSource implements ImageSource
|
||||
var _ ImageSource = (*CDNSource)(nil)
|
||||
}
|
||||
|
||||
func TestCDNSource_Config(t *testing.T) {
|
||||
func TestCDNSource_Config_Good(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
CDNURL: "https://cdn.example.com",
|
||||
ImageName: "my-image.qcow2",
|
||||
|
|
@ -251,7 +251,7 @@ func TestCDNSource_Config(t *testing.T) {
|
|||
assert.Equal(t, "my-image.qcow2", src.config.ImageName)
|
||||
}
|
||||
|
||||
func TestNewCDNSource_Good(t *testing.T) {
|
||||
func TestCDN_NewCDNSource_Good(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "host-uk/core-images",
|
||||
RegistryImage: "ghcr.io/host-uk/core-devops",
|
||||
|
|
@ -265,7 +265,7 @@ func TestNewCDNSource_Good(t *testing.T) {
|
|||
assert.Equal(t, cfg.CDNURL, src.config.CDNURL)
|
||||
}
|
||||
|
||||
func TestCDNSource_Download_Good_CreatesDestDir(t *testing.T) {
|
||||
func TestCDNSource_Download_CreatesDestDir_Good(t *testing.T) {
|
||||
content := "test content"
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
|
@ -291,7 +291,7 @@ func TestCDNSource_Download_Good_CreatesDestDir(t *testing.T) {
|
|||
assert.True(t, info.IsDir())
|
||||
}
|
||||
|
||||
func TestSourceConfig_Struct(t *testing.T) {
|
||||
func TestSourceConfig_Struct_Good(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "owner/repo",
|
||||
RegistryImage: "ghcr.io/owner/image",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ type GitHubSource struct {
|
|||
var _ ImageSource = (*GitHubSource)(nil)
|
||||
|
||||
// NewGitHubSource creates a new GitHub source.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// src := NewGitHubSource(cfg)
|
||||
func NewGitHubSource(cfg SourceConfig) *GitHubSource {
|
||||
return &GitHubSource{config: cfg}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGitHubSource_Good_Available(t *testing.T) {
|
||||
func TestGitHubSource_Available_Good(t *testing.T) {
|
||||
src := NewGitHubSource(SourceConfig{
|
||||
GitHubRepo: "host-uk/core-images",
|
||||
ImageName: "core-devops-darwin-arm64.qcow2",
|
||||
|
|
@ -20,12 +20,12 @@ func TestGitHubSource_Good_Available(t *testing.T) {
|
|||
_ = src.Available()
|
||||
}
|
||||
|
||||
func TestGitHubSource_Name(t *testing.T) {
|
||||
func TestGitHubSource_Name_Good(t *testing.T) {
|
||||
src := NewGitHubSource(SourceConfig{})
|
||||
assert.Equal(t, "github", src.Name())
|
||||
}
|
||||
|
||||
func TestGitHubSource_Config(t *testing.T) {
|
||||
func TestGitHubSource_Config_Good(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "owner/repo",
|
||||
ImageName: "test-image.qcow2",
|
||||
|
|
@ -37,7 +37,7 @@ func TestGitHubSource_Config(t *testing.T) {
|
|||
assert.Equal(t, "test-image.qcow2", src.config.ImageName)
|
||||
}
|
||||
|
||||
func TestGitHubSource_Good_Multiple(t *testing.T) {
|
||||
func TestGitHubSource_Multiple_Good(t *testing.T) {
|
||||
// Test creating multiple sources with different configs
|
||||
src1 := NewGitHubSource(SourceConfig{GitHubRepo: "org1/repo1", ImageName: "img1.qcow2"})
|
||||
src2 := NewGitHubSource(SourceConfig{GitHubRepo: "org2/repo2", ImageName: "img2.qcow2"})
|
||||
|
|
@ -48,7 +48,7 @@ func TestGitHubSource_Good_Multiple(t *testing.T) {
|
|||
assert.Equal(t, "github", src2.Name())
|
||||
}
|
||||
|
||||
func TestNewGitHubSource_Good(t *testing.T) {
|
||||
func TestGitHub_NewGitHubSource_Good(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "host-uk/core-images",
|
||||
RegistryImage: "ghcr.io/host-uk/core-devops",
|
||||
|
|
@ -62,7 +62,7 @@ func TestNewGitHubSource_Good(t *testing.T) {
|
|||
assert.Equal(t, cfg.GitHubRepo, src.config.GitHubRepo)
|
||||
}
|
||||
|
||||
func TestGitHubSource_InterfaceCompliance(t *testing.T) {
|
||||
func TestGitHubSource_InterfaceCompliance_Good(t *testing.T) {
|
||||
// Verify GitHubSource implements ImageSource
|
||||
var _ ImageSource = (*GitHubSource)(nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSourceConfig_Empty(t *testing.T) {
|
||||
func TestSourceConfig_Empty_Good(t *testing.T) {
|
||||
cfg := SourceConfig{}
|
||||
assert.Empty(t, cfg.GitHubRepo)
|
||||
assert.Empty(t, cfg.RegistryImage)
|
||||
|
|
@ -14,7 +14,7 @@ func TestSourceConfig_Empty(t *testing.T) {
|
|||
assert.Empty(t, cfg.ImageName)
|
||||
}
|
||||
|
||||
func TestSourceConfig_Complete(t *testing.T) {
|
||||
func TestSourceConfig_Complete_Good(t *testing.T) {
|
||||
cfg := SourceConfig{
|
||||
GitHubRepo: "owner/repo",
|
||||
RegistryImage: "ghcr.io/owner/image:v1",
|
||||
|
|
@ -28,7 +28,7 @@ func TestSourceConfig_Complete(t *testing.T) {
|
|||
assert.Equal(t, "my-image-darwin-arm64.qcow2", cfg.ImageName)
|
||||
}
|
||||
|
||||
func TestImageSource_Interface(t *testing.T) {
|
||||
func TestImageSource_Interface_Good(t *testing.T) {
|
||||
// Ensure both sources implement the interface
|
||||
var _ ImageSource = (*GitHubSource)(nil)
|
||||
var _ ImageSource = (*CDNSource)(nil)
|
||||
|
|
|
|||
28
state.go
28
state.go
|
|
@ -20,6 +20,10 @@ type State struct {
|
|||
}
|
||||
|
||||
// DefaultStateDir returns the default directory for state files (~/.core).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// dir, err := DefaultStateDir()
|
||||
func DefaultStateDir() (string, error) {
|
||||
home := coreutil.HomeDir()
|
||||
if home == "" {
|
||||
|
|
@ -29,6 +33,10 @@ func DefaultStateDir() (string, error) {
|
|||
}
|
||||
|
||||
// DefaultStatePath returns the default path for the state file.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// path, err := DefaultStatePath()
|
||||
func DefaultStatePath() (string, error) {
|
||||
dir, err := DefaultStateDir()
|
||||
if err != nil {
|
||||
|
|
@ -38,6 +46,10 @@ func DefaultStatePath() (string, error) {
|
|||
}
|
||||
|
||||
// DefaultLogsDir returns the default directory for container logs.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// dir, err := DefaultLogsDir()
|
||||
func DefaultLogsDir() (string, error) {
|
||||
dir, err := DefaultStateDir()
|
||||
if err != nil {
|
||||
|
|
@ -47,6 +59,10 @@ func DefaultLogsDir() (string, error) {
|
|||
}
|
||||
|
||||
// NewState creates a new State instance.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// state := NewState("/tmp/containers.json")
|
||||
func NewState(filePath string) *State {
|
||||
return &State{
|
||||
Containers: make(map[string]*Container),
|
||||
|
|
@ -56,6 +72,10 @@ func NewState(filePath string) *State {
|
|||
|
||||
// LoadState loads the state from the given file path.
|
||||
// If the file doesn't exist, returns an empty state.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// state, err := LoadState("/tmp/containers.json")
|
||||
func LoadState(filePath string) (*State, error) {
|
||||
state := NewState(filePath)
|
||||
|
||||
|
|
@ -156,6 +176,10 @@ func (s *State) FilePath() string {
|
|||
}
|
||||
|
||||
// LogPath returns the log file path for a given container ID.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// path, err := LogPath(containerID)
|
||||
func LogPath(id string) (string, error) {
|
||||
logsDir, err := DefaultLogsDir()
|
||||
if err != nil {
|
||||
|
|
@ -165,6 +189,10 @@ func LogPath(id string) (string, error) {
|
|||
}
|
||||
|
||||
// EnsureLogsDir ensures the logs directory exists.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := EnsureLogsDir()
|
||||
func EnsureLogsDir() error {
|
||||
logsDir, err := DefaultLogsDir()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewState_Good(t *testing.T) {
|
||||
func TestState_NewState_Good(t *testing.T) {
|
||||
state := NewState("/tmp/test-state.json")
|
||||
|
||||
assert.NotNil(t, state)
|
||||
|
|
@ -20,7 +20,7 @@ func TestNewState_Good(t *testing.T) {
|
|||
assert.Equal(t, "/tmp/test-state.json", state.FilePath())
|
||||
}
|
||||
|
||||
func TestLoadState_Good_NewFile(t *testing.T) {
|
||||
func TestLoadState_NewFile_Good(t *testing.T) {
|
||||
// Test loading from non-existent file
|
||||
tmpDir := t.TempDir()
|
||||
statePath := coreutil.JoinPath(tmpDir, "containers.json")
|
||||
|
|
@ -32,7 +32,7 @@ func TestLoadState_Good_NewFile(t *testing.T) {
|
|||
assert.Empty(t, state.Containers)
|
||||
}
|
||||
|
||||
func TestLoadState_Good_ExistingFile(t *testing.T) {
|
||||
func TestLoadState_ExistingFile_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
statePath := coreutil.JoinPath(tmpDir, "containers.json")
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ func TestLoadState_Good_ExistingFile(t *testing.T) {
|
|||
assert.Equal(t, StatusRunning, c.Status)
|
||||
}
|
||||
|
||||
func TestLoadState_Bad_InvalidJSON(t *testing.T) {
|
||||
func TestLoadState_InvalidJSON_Bad(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
statePath := coreutil.JoinPath(tmpDir, "containers.json")
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ func TestState_Remove_Good(t *testing.T) {
|
|||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestState_Get_Bad_NotFound(t *testing.T) {
|
||||
func TestState_Get_NotFound_Bad(t *testing.T) {
|
||||
state := NewState("/tmp/test-state.json")
|
||||
|
||||
_, ok := state.Get("nonexistent")
|
||||
|
|
@ -160,7 +160,7 @@ func TestState_All_Good(t *testing.T) {
|
|||
assert.Len(t, all, 3)
|
||||
}
|
||||
|
||||
func TestState_SaveState_Good_CreatesDirectory(t *testing.T) {
|
||||
func TestState_SaveState_CreatesDirectory_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
nestedPath := coreutil.JoinPath(tmpDir, "nested", "dir", "containers.json")
|
||||
state := NewState(nestedPath)
|
||||
|
|
@ -174,31 +174,31 @@ func TestState_SaveState_Good_CreatesDirectory(t *testing.T) {
|
|||
assert.True(t, io.Local.IsDir(core.PathDir(nestedPath)))
|
||||
}
|
||||
|
||||
func TestDefaultStateDir_Good(t *testing.T) {
|
||||
func TestState_DefaultStateDir_Good(t *testing.T) {
|
||||
dir, err := DefaultStateDir()
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, dir, ".core")
|
||||
}
|
||||
|
||||
func TestDefaultStatePath_Good(t *testing.T) {
|
||||
func TestState_DefaultStatePath_Good(t *testing.T) {
|
||||
path, err := DefaultStatePath()
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, path, "containers.json")
|
||||
}
|
||||
|
||||
func TestDefaultLogsDir_Good(t *testing.T) {
|
||||
func TestState_DefaultLogsDir_Good(t *testing.T) {
|
||||
dir, err := DefaultLogsDir()
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, dir, "logs")
|
||||
}
|
||||
|
||||
func TestLogPath_Good(t *testing.T) {
|
||||
func TestState_LogPath_Good(t *testing.T) {
|
||||
path, err := LogPath("abc12345")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, path, "abc12345.log")
|
||||
}
|
||||
|
||||
func TestEnsureLogsDir_Good(t *testing.T) {
|
||||
func TestState_EnsureLogsDir_Good(t *testing.T) {
|
||||
// This test creates real directories - skip in CI if needed
|
||||
err := EnsureLogsDir()
|
||||
assert.NoError(t, err)
|
||||
|
|
@ -207,7 +207,7 @@ func TestEnsureLogsDir_Good(t *testing.T) {
|
|||
assert.True(t, io.Local.IsDir(logsDir))
|
||||
}
|
||||
|
||||
func TestGenerateID_Good(t *testing.T) {
|
||||
func TestState_GenerateID_Good(t *testing.T) {
|
||||
id1, err := GenerateID()
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, id1, 8)
|
||||
|
|
|
|||
24
templates.go
24
templates.go
|
|
@ -44,11 +44,19 @@ var builtinTemplates = []Template{
|
|||
// ListTemplates returns all available LinuxKit templates.
|
||||
// It combines embedded templates with any templates found in the user's
|
||||
// .core/linuxkit directory.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// templates := ListTemplates()
|
||||
func ListTemplates() []Template {
|
||||
return slices.Collect(ListTemplatesIter())
|
||||
}
|
||||
|
||||
// ListTemplatesIter returns an iterator for all available LinuxKit templates.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// for template := range ListTemplatesIter() { _ = template }
|
||||
func ListTemplatesIter() iter.Seq[Template] {
|
||||
return func(yield func(Template) bool) {
|
||||
// Yield builtin templates
|
||||
|
|
@ -72,6 +80,10 @@ func ListTemplatesIter() iter.Seq[Template] {
|
|||
|
||||
// GetTemplate returns the content of a template by name.
|
||||
// It first checks embedded templates, then user templates.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// content, err := GetTemplate("core-dev")
|
||||
func GetTemplate(name string) (string, error) {
|
||||
// Check embedded templates first
|
||||
for _, t := range builtinTemplates {
|
||||
|
|
@ -104,6 +116,10 @@ func GetTemplate(name string) (string, error) {
|
|||
// It supports two syntaxes:
|
||||
// - ${VAR} - required variable, returns error if not provided
|
||||
// - ${VAR:-default} - variable with default value
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// content, err := ApplyTemplate("core-dev", vars)
|
||||
func ApplyTemplate(name string, vars map[string]string) (string, error) {
|
||||
content, err := GetTemplate(name)
|
||||
if err != nil {
|
||||
|
|
@ -117,6 +133,10 @@ func ApplyTemplate(name string, vars map[string]string) (string, error) {
|
|||
// It supports two syntaxes:
|
||||
// - ${VAR} - required variable, returns error if not provided
|
||||
// - ${VAR:-default} - variable with default value
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// content, err := ApplyVariables(raw, vars)
|
||||
func ApplyVariables(content string, vars map[string]string) (string, error) {
|
||||
// Pattern for ${VAR:-default} syntax
|
||||
defaultPattern := regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*):-([^}]*)\}`)
|
||||
|
|
@ -166,6 +186,10 @@ func ApplyVariables(content string, vars map[string]string) (string, error) {
|
|||
|
||||
// ExtractVariables extracts all variable names from a template.
|
||||
// Returns two slices: required variables and optional variables (with defaults).
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// required, optional := ExtractVariables(content)
|
||||
func ExtractVariables(content string) (required []string, optional map[string]string) {
|
||||
optional = make(map[string]string)
|
||||
requiredSet := make(map[string]bool)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestListTemplates_Good(t *testing.T) {
|
||||
func TestTemplates_ListTemplates_Good(t *testing.T) {
|
||||
templates := ListTemplates()
|
||||
|
||||
// Should have at least the builtin templates
|
||||
|
|
@ -42,7 +42,7 @@ func TestListTemplates_Good(t *testing.T) {
|
|||
assert.True(t, found, "server-php template should exist")
|
||||
}
|
||||
|
||||
func TestGetTemplate_Good_CoreDev(t *testing.T) {
|
||||
func TestGetTemplate_CoreDev_Good(t *testing.T) {
|
||||
content, err := GetTemplate("core-dev")
|
||||
|
||||
require.NoError(t, err)
|
||||
|
|
@ -53,7 +53,7 @@ func TestGetTemplate_Good_CoreDev(t *testing.T) {
|
|||
assert.Contains(t, content, "services:")
|
||||
}
|
||||
|
||||
func TestGetTemplate_Good_ServerPhp(t *testing.T) {
|
||||
func TestGetTemplate_ServerPhp_Good(t *testing.T) {
|
||||
content, err := GetTemplate("server-php")
|
||||
|
||||
require.NoError(t, err)
|
||||
|
|
@ -64,14 +64,14 @@ func TestGetTemplate_Good_ServerPhp(t *testing.T) {
|
|||
assert.Contains(t, content, "${DOMAIN:-localhost}")
|
||||
}
|
||||
|
||||
func TestGetTemplate_Bad_NotFound(t *testing.T) {
|
||||
func TestGetTemplate_NotFound_Bad(t *testing.T) {
|
||||
_, err := GetTemplate("nonexistent-template")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "template not found")
|
||||
}
|
||||
|
||||
func TestApplyVariables_Good_SimpleSubstitution(t *testing.T) {
|
||||
func TestApplyVariables_SimpleSubstitution_Good(t *testing.T) {
|
||||
content := "Hello ${NAME}, welcome to ${PLACE}!"
|
||||
vars := map[string]string{
|
||||
"NAME": "World",
|
||||
|
|
@ -84,7 +84,7 @@ func TestApplyVariables_Good_SimpleSubstitution(t *testing.T) {
|
|||
assert.Equal(t, "Hello World, welcome to Core!", result)
|
||||
}
|
||||
|
||||
func TestApplyVariables_Good_WithDefaults(t *testing.T) {
|
||||
func TestApplyVariables_WithDefaults_Good(t *testing.T) {
|
||||
content := "Memory: ${MEMORY:-1024}MB, CPUs: ${CPUS:-2}"
|
||||
vars := map[string]string{
|
||||
"MEMORY": "2048",
|
||||
|
|
@ -97,7 +97,7 @@ func TestApplyVariables_Good_WithDefaults(t *testing.T) {
|
|||
assert.Equal(t, "Memory: 2048MB, CPUs: 2", result)
|
||||
}
|
||||
|
||||
func TestApplyVariables_Good_AllDefaults(t *testing.T) {
|
||||
func TestApplyVariables_AllDefaults_Good(t *testing.T) {
|
||||
content := "${HOST:-localhost}:${PORT:-8080}"
|
||||
vars := map[string]string{} // No vars provided
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ func TestApplyVariables_Good_AllDefaults(t *testing.T) {
|
|||
assert.Equal(t, "localhost:8080", result)
|
||||
}
|
||||
|
||||
func TestApplyVariables_Good_MixedSyntax(t *testing.T) {
|
||||
func TestApplyVariables_MixedSyntax_Good(t *testing.T) {
|
||||
content := `
|
||||
hostname: ${HOSTNAME:-myhost}
|
||||
ssh_key: ${SSH_KEY}
|
||||
|
|
@ -126,7 +126,7 @@ memory: ${MEMORY:-512}
|
|||
assert.Contains(t, result, "memory: 512")
|
||||
}
|
||||
|
||||
func TestApplyVariables_Good_EmptyDefault(t *testing.T) {
|
||||
func TestApplyVariables_EmptyDefault_Good(t *testing.T) {
|
||||
content := "value: ${OPT:-}"
|
||||
vars := map[string]string{}
|
||||
|
||||
|
|
@ -136,7 +136,7 @@ func TestApplyVariables_Good_EmptyDefault(t *testing.T) {
|
|||
assert.Equal(t, "value: ", result)
|
||||
}
|
||||
|
||||
func TestApplyVariables_Bad_MissingRequired(t *testing.T) {
|
||||
func TestApplyVariables_MissingRequired_Bad(t *testing.T) {
|
||||
content := "SSH Key: ${SSH_KEY}"
|
||||
vars := map[string]string{} // Missing required SSH_KEY
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ func TestApplyVariables_Bad_MissingRequired(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "SSH_KEY")
|
||||
}
|
||||
|
||||
func TestApplyVariables_Bad_MultipleMissing(t *testing.T) {
|
||||
func TestApplyVariables_MultipleMissing_Bad(t *testing.T) {
|
||||
content := "${VAR1} and ${VAR2} and ${VAR3}"
|
||||
vars := map[string]string{
|
||||
"VAR2": "provided",
|
||||
|
|
@ -162,7 +162,7 @@ func TestApplyVariables_Bad_MultipleMissing(t *testing.T) {
|
|||
assert.True(t, core.Contains(errStr, "VAR1") || core.Contains(errStr, "VAR3"))
|
||||
}
|
||||
|
||||
func TestApplyTemplate_Good(t *testing.T) {
|
||||
func TestTemplates_ApplyTemplate_Good(t *testing.T) {
|
||||
vars := map[string]string{
|
||||
"SSH_KEY": "ssh-rsa AAAA... user@host",
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ func TestApplyTemplate_Good(t *testing.T) {
|
|||
assert.Contains(t, result, "core-dev") // HOSTNAME default
|
||||
}
|
||||
|
||||
func TestApplyTemplate_Bad_TemplateNotFound(t *testing.T) {
|
||||
func TestApplyTemplate_TemplateNotFound_Bad(t *testing.T) {
|
||||
vars := map[string]string{
|
||||
"SSH_KEY": "test",
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ func TestApplyTemplate_Bad_TemplateNotFound(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "template not found")
|
||||
}
|
||||
|
||||
func TestApplyTemplate_Bad_MissingVariable(t *testing.T) {
|
||||
func TestApplyTemplate_MissingVariable_Bad(t *testing.T) {
|
||||
// server-php requires SSH_KEY
|
||||
vars := map[string]string{} // Missing required SSH_KEY
|
||||
|
||||
|
|
@ -197,7 +197,7 @@ func TestApplyTemplate_Bad_MissingVariable(t *testing.T) {
|
|||
assert.Contains(t, err.Error(), "missing required variables")
|
||||
}
|
||||
|
||||
func TestExtractVariables_Good(t *testing.T) {
|
||||
func TestTemplates_ExtractVariables_Good(t *testing.T) {
|
||||
content := `
|
||||
hostname: ${HOSTNAME:-myhost}
|
||||
ssh_key: ${SSH_KEY}
|
||||
|
|
@ -219,7 +219,7 @@ api_key: ${API_KEY}
|
|||
assert.Len(t, optional, 3)
|
||||
}
|
||||
|
||||
func TestExtractVariables_Good_NoVariables(t *testing.T) {
|
||||
func TestExtractVariables_NoVariables_Good(t *testing.T) {
|
||||
content := "This has no variables at all"
|
||||
|
||||
required, optional := ExtractVariables(content)
|
||||
|
|
@ -228,7 +228,7 @@ func TestExtractVariables_Good_NoVariables(t *testing.T) {
|
|||
assert.Empty(t, optional)
|
||||
}
|
||||
|
||||
func TestExtractVariables_Good_OnlyDefaults(t *testing.T) {
|
||||
func TestExtractVariables_OnlyDefaults_Good(t *testing.T) {
|
||||
content := "${A:-default1} ${B:-default2}"
|
||||
|
||||
required, optional := ExtractVariables(content)
|
||||
|
|
@ -239,7 +239,7 @@ func TestExtractVariables_Good_OnlyDefaults(t *testing.T) {
|
|||
assert.Equal(t, "default2", optional["B"])
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good(t *testing.T) {
|
||||
func TestTemplates_ScanUserTemplates_Good(t *testing.T) {
|
||||
// Create a temporary directory with template files
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
|
|
@ -263,7 +263,7 @@ kernel:
|
|||
assert.Equal(t, "My Custom Template", templates[0].Description)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_MultipleTemplates(t *testing.T) {
|
||||
func TestScanUserTemplates_MultipleTemplates_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create multiple template files
|
||||
|
|
@ -285,7 +285,7 @@ func TestScanUserTemplates_Good_MultipleTemplates(t *testing.T) {
|
|||
assert.True(t, names["db"])
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_EmptyDirectory(t *testing.T) {
|
||||
func TestScanUserTemplates_EmptyDirectory_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
templates := scanUserTemplates(tmpDir)
|
||||
|
|
@ -293,13 +293,13 @@ func TestScanUserTemplates_Good_EmptyDirectory(t *testing.T) {
|
|||
assert.Empty(t, templates)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Bad_NonexistentDirectory(t *testing.T) {
|
||||
func TestScanUserTemplates_NonexistentDirectory_Bad(t *testing.T) {
|
||||
templates := scanUserTemplates("/nonexistent/path/to/templates")
|
||||
|
||||
assert.Empty(t, templates)
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Good(t *testing.T) {
|
||||
func TestTemplates_ExtractTemplateDescription_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "test.yml")
|
||||
|
||||
|
|
@ -316,7 +316,7 @@ kernel:
|
|||
assert.Equal(t, "My Template Description", desc)
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Good_NoComments(t *testing.T) {
|
||||
func TestExtractTemplateDescription_NoComments_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "test.yml")
|
||||
|
||||
|
|
@ -331,13 +331,13 @@ func TestExtractTemplateDescription_Good_NoComments(t *testing.T) {
|
|||
assert.Empty(t, desc)
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Bad_FileNotFound(t *testing.T) {
|
||||
func TestExtractTemplateDescription_FileNotFound_Bad(t *testing.T) {
|
||||
desc := extractTemplateDescription("/nonexistent/file.yml")
|
||||
|
||||
assert.Empty(t, desc)
|
||||
}
|
||||
|
||||
func TestVariablePatternEdgeCases_Good(t *testing.T) {
|
||||
func TestTemplates_VariablePatternEdgeCases_Good(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
content string
|
||||
|
|
@ -385,7 +385,7 @@ func TestVariablePatternEdgeCases_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_SkipsBuiltinNames(t *testing.T) {
|
||||
func TestScanUserTemplates_SkipsBuiltinNames_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a template with a builtin name (should be skipped)
|
||||
|
|
@ -403,7 +403,7 @@ func TestScanUserTemplates_Good_SkipsBuiltinNames(t *testing.T) {
|
|||
assert.Equal(t, "unique", templates[0].Name)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_SkipsDirectories(t *testing.T) {
|
||||
func TestScanUserTemplates_SkipsDirectories_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a subdirectory (should be skipped)
|
||||
|
|
@ -420,7 +420,7 @@ func TestScanUserTemplates_Good_SkipsDirectories(t *testing.T) {
|
|||
assert.Equal(t, "valid", templates[0].Name)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_YamlExtension(t *testing.T) {
|
||||
func TestScanUserTemplates_YamlExtension_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create templates with both extensions
|
||||
|
|
@ -441,7 +441,7 @@ func TestScanUserTemplates_Good_YamlExtension(t *testing.T) {
|
|||
assert.True(t, names["template2"])
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Good_EmptyComment(t *testing.T) {
|
||||
func TestExtractTemplateDescription_EmptyComment_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "test.yml")
|
||||
|
||||
|
|
@ -459,7 +459,7 @@ kernel:
|
|||
assert.Equal(t, "Actual description here", desc)
|
||||
}
|
||||
|
||||
func TestExtractTemplateDescription_Good_MultipleEmptyComments(t *testing.T) {
|
||||
func TestExtractTemplateDescription_MultipleEmptyComments_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
path := coreutil.JoinPath(tmpDir, "test.yml")
|
||||
|
||||
|
|
@ -479,7 +479,7 @@ kernel:
|
|||
assert.Equal(t, "Real description", desc)
|
||||
}
|
||||
|
||||
func TestScanUserTemplates_Good_DefaultDescription(t *testing.T) {
|
||||
func TestScanUserTemplates_DefaultDescription_Good(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a template without comments
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue