platform

package
v0.58.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 16 Imported by: 0

README ΒΆ

Blueprint Platform Abstraction Layer

This package provides the foundational testing infrastructure for Blueprint, enabling fast, reliable unit tests by separating I/O and system dependencies from business logic.

Architecture Overview

The platform abstraction layer follows a dependency injection pattern with clearly defined interfaces for all external dependencies:

                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                           β”‚   Handlers      β”‚
                           β”‚ (Business Logic)β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                           β”‚    Container    β”‚
                           β”‚ (Dependency     β”‚
                           β”‚  Injection)     β”‚
                           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                    β”‚
                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                    β”‚               β”‚               β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚ SystemProviderβ”‚ β”‚  GitProvider  β”‚ β”‚CryptoProvider β”‚
            β”‚               β”‚ β”‚               β”‚ β”‚               β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    β”‚
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”
            β”‚       β”‚       β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚OSDetectorβ”‚ β”‚Process  β”‚ β”‚Filesystemβ”‚
    β”‚         β”‚ β”‚Executor β”‚ β”‚Provider β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Components

Interfaces (interfaces.go)
  • SystemProvider: Main interface combining all platform operations
  • OSDetector: Operating system detection capabilities
  • ProcessExecutor: Command execution and process management
  • FilesystemProvider: File and directory operations
  • NetworkProvider: HTTP requests and network operations
  • GitProvider: Git-specific operations
  • CryptoProvider: Encryption/decryption operations
Container (container.go)
  • Container: Dependency injection container interface
  • NewContainer(): Creates production container with real implementations
  • NewTestContainer(): Creates test container for mock injection
  • TestContainer: Fluent interface for test configuration
Mocks (mocks/)

All mocks provide fluent interfaces for easy test configuration:

  • MockSystemProvider: Complete system mock with fluent API
  • MockOSDetector: OS detection mocking
  • MockProcessExecutor: Command execution mocking
  • MockFilesystemProvider: In-memory filesystem
  • MockGitProvider: Git operations mocking

Example usage:

systemProvider := mocks.NewMockSystemProvider().
    WithOS("linux").
    WithUser("testuser", "1000", "1000", "/home/testuser").
    WithCommandResult("git clone ...", &platform.ExecuteResult{
        ExitCode: 0,
        Stdout:   "Cloned successfully",
        Success:  true,
    }).
    WithFile("/home/testuser/.gitconfig", []byte("git config"))
Test Utilities (testutils/)
Builders (builders.go)

Fluent builders for common test objects:

  • RuleBuilder: Creates parser.Rule objects
  • StatusBuilder: Creates handlers.Status objects
  • ExecutionRecordBuilder: Creates handlers.ExecutionRecord objects
  • ExecuteResultBuilder: Creates platform.ExecuteResult objects

Example:

rule := testutils.NewRule().
    WithAction("install").
    WithPackages("git", "curl").
    WithOS("mac").
    Build()

status := testutils.NewStatus().
    WithPackage("git", "/tmp/test.bp", "mac").
    WithClone("https://github.com/user/repo", "~/repo", "abc123", "/tmp/test.bp", "mac").
    Build()
Assertions (assertions.go)

Fluent assertion helpers:

  • AssertExecuteResult: Assertions for command execution results
  • AssertStatus: Assertions for status objects
  • AssertString: String assertions
  • AssertError: Error assertions

Example:

testutils.NewAssertExecuteResult(t, result, "git clone").
    IsSuccess().
    HasStdout("Cloned").
    HasExitCode(0)

testutils.NewAssertStatus(t, &status, "status").
    HasPackageCount(2).
    HasPackage("git", "/tmp/test.bp", "mac").
    DoesNotHaveClone("https://other.com/repo", "~/other", "/tmp/test.bp", "mac")

Testing Strategy

Pure Function Tests

Test business logic without any I/O or external dependencies:

func TestHandler_GetCommand_Pure(t *testing.T) {
    rule := testutils.NewRule().WithPackage("curl").Build()
    handler := handlers.NewInstallHandler(rule, "")
    
    cmd := handler.GetCommand()
    
    testutils.AssertStringEquals(t, cmd, "brew install curl", "command")
}

Performance: <100ns per test

Mocked I/O Tests

Test business logic with controlled external dependencies:

func TestHandler_Up_WithMocks(t *testing.T) {
    system := mocks.NewMockSystemProvider().
        WithOS("linux").
        WithCommandResult("apt-get install curl", successResult)
    
    container := platform.NewTestContainer().
        WithSystemProvider(system).
        Build()
    
    handler := handlers.NewInstallHandlerWithContainer(rule, "", container)
    
    result, err := handler.Up()
    
    testutils.AssertNoError(t, err, "Up")
    testutils.AssertStringContains(t, result, "installed", "result")
}

Performance: <1ms per test

Integration Tests

Test actual system interactions (kept in internal/handlers/integration/):

func TestHandler_Up_Integration(t *testing.T) {
    if testing.Short() {
        t.Skip("skipping integration test")
    }
    
    // Test with real system dependencies
    handler := handlers.NewInstallHandler(rule, "")
    result, err := handler.Up()
    
    // Assertions...
}

Current Status

βœ… Completed (Phase 1)
  • Platform abstraction interfaces
  • Dependency injection container
  • Mock infrastructure with fluent APIs
  • Test package structure
  • Test utilities and builders
  • Example unit tests demonstrating new patterns
  • Performance optimization (<1ms per unit test)
🚧 Next Steps (Phase 2)
  • Refactor handlers to use platform abstractions
  • Implement real platform providers
  • Convert existing integration tests to unit tests
  • Add dependency injection to handler constructors
  • Update engine to use container
πŸ“Š Performance Metrics
  • Pure function tests: ~93ns per operation
  • Mocked I/O tests: <1ms per test
  • Zero external dependencies in unit tests
  • Fast feedback for developers

Usage Examples

Setting Up Tests
func TestMyHandler(t *testing.T) {
    // Create test data
    rule := testutils.NewRule().WithPackage("git").Build()
    
    // Create mocked dependencies
    system := mocks.NewMockSystemProvider().
        WithOS("mac").
        WithCommandResult("brew install git", successResult)
    
    // Create container with mocks
    container := platform.NewTestContainer().
        WithSystemProvider(system).
        Build()
    
    // Create handler with dependencies
    handler := NewMyHandlerWithContainer(rule, container)
    
    // Test business logic
    result, err := handler.Up()
    
    // Assertions
    testutils.AssertNoError(t, err, "Up")
}
Common Test Patterns
  1. Pure Business Logic: Test functions that don't require I/O
  2. Mocked Dependencies: Test with controlled external dependencies
  3. Integration: Test actual system interactions (sparingly)
Fluent Mocking
// Git operations
git := mocks.NewMockGitProvider().
    WithSuccessfulClone(url, path, branch, sha).
    WithUpToDateRepository(path, branch, sha).
    WithUnreachableRemote(url, branch)

// Filesystem operations  
fs := mocks.NewMockFilesystemProvider().
    WithFile("/tmp/test.txt", []byte("content")).
    WithDirectory("/tmp").
    WithPermissions("/tmp/test.txt", 0644)

// Command execution
proc := mocks.NewMockProcessExecutor().
    WithCommandResult("git clone ...", successResult).
    WithCommandError("invalid-command", errors.New("not found"))

This foundation enables fast, reliable testing while maintaining clear separation between business logic and external dependencies.

Documentation ΒΆ

Overview ΒΆ

Package platform provides abstractions for system operations to enable testability. This package separates I/O and system dependencies from business logic, allowing for fast, reliable unit testing with mocks.

Index ΒΆ

Constants ΒΆ

This section is empty.

Variables ΒΆ

View Source
var (
	// NewSystemProvider creates a real system provider
	NewSystemProvider = func() SystemProvider {
		return &realSystemProvider{
			osDetector:         &realOSDetector{},
			processExecutor:    &realProcessExecutor{},
			filesystemProvider: &realFilesystemProvider{},
			networkProvider:    &realNetworkProvider{},
		}
	}

	// NewGitProvider creates a real git provider
	NewGitProvider = func() GitProvider {
		return &realGitProvider{}
	}

	// NewCryptoProvider creates a real crypto provider
	NewCryptoProvider = func() CryptoProvider {
		return &realCryptoProvider{}
	}

	// NewPackageManagerProvider creates a real package manager provider
	NewPackageManagerProvider = func() PackageManagerProvider {
		return &realPackageManagerProvider{}
	}
)

Functions ΒΆ

This section is empty.

Types ΒΆ

type CheckoutSpec ΒΆ added in v0.56.3

type CheckoutSpec struct {
	Path RepoPath
	SHA  SHA
}

CheckoutSpec is doctor's exact-SHA comparison. Force/Detach are realistic future fields (the current internal impl already forces) β€” spec type from day one so adding them touches zero call sites.

type CloneMode ΒΆ added in v0.56.3

type CloneMode uint8

CloneMode selects the clone strategy. The zero value is ModeTwoStage, the engine's default, so a spec constructed without an explicit mode does what clone rules do today.

const (
	// ModeTwoStage clones into clean storage and mirrors the content into the
	// target (no .git in the target β€” it is a mirror, not a working repo).
	ModeTwoStage CloneMode = iota
	// ModeDirect clones a fully functional working copy. Updating an existing
	// copy reverts tracked changes, but untracked files MUST survive (#031).
	ModeDirect
	// ModeBare keeps the repository itself in <path>/.git (bare) with a
	// worktree per branch under <path>/; updates never touch existing
	// worktrees.
	ModeBare
)

type CloneResult ΒΆ added in v0.56.3

type CloneResult struct {
	Status CloneStatus
	OldSHA SHA
	NewSHA SHA
	// Notes carries recoverable incidents (repair counts, #031
	// untracked-file protection warnings). Never a failure β€” failures are
	// errors.
	Notes []string
}

CloneResult reports what a Clone did.

type CloneSpec ΒΆ added in v0.56.3

type CloneSpec struct {
	URL    string
	Path   string
	Branch string
	Mode   CloneMode // ModeTwoStage (default) | ModeDirect | ModeBare
}

CloneSpec is resolved intent: the caller interpreted the rule, the engine executes the spec. Shorthand/`~` expansion belong to the engine.

type CloneStatus ΒΆ added in v0.56.3

type CloneStatus uint8

CloneStatus enumerates what a Clone did. Callers switch on the enum β€” never on status strings β€” so the compiler enumerates every case.

const (
	StatusCloned   CloneStatus = iota // target did not exist; created
	StatusUpdated                     // existing target moved to a new SHA
	StatusSynced                      // content resynced, SHA unchanged
	StatusUpToDate                    // nothing to do
)

type CommandExecutor ΒΆ

type CommandExecutor interface {
	Execute(cmd string) (string, error)
}

CommandExecutor interface for executing commands - allows dependency injection This replaces the old testMode pattern with clean dependency injection

type Container ΒΆ

type Container interface {
	// SystemProvider returns the system provider instance
	SystemProvider() SystemProvider
	// GitProvider returns the git provider instance
	GitProvider() GitProvider
	// CryptoProvider returns the crypto provider instance
	CryptoProvider() CryptoProvider
	// PackageManagerProvider returns the package manager provider instance
	PackageManagerProvider() PackageManagerProvider
}

Container interface for dependency injection

func NewContainer ΒΆ

func NewContainer() Container

NewContainer creates a new production container with real implementations.

type CryptoProvider ΒΆ

type CryptoProvider interface {
	// Decrypt decrypts a file using the provided password
	Decrypt(inputPath, outputPath, password string) error
	// Encrypt encrypts a file using the provided password
	Encrypt(inputPath, outputPath, password string) error
	// IsEncrypted checks if a file appears to be encrypted
	IsEncrypted(path string) bool
}

CryptoProvider handles encryption and decryption operations.

type ExecuteOptions ΒΆ

type ExecuteOptions struct {
	// WorkingDir sets the working directory for the command
	WorkingDir string
	// Environment provides additional environment variables
	Environment map[string]string
	// Input provides stdin input to the command
	Input string
	// StreamOutput determines if output should be streamed live
	StreamOutput bool
	// SudoPassword provides sudo password if needed
	SudoPassword string
}

ExecuteOptions configures command execution

type ExecuteResult ΒΆ

type ExecuteResult struct {
	// ExitCode is the process exit code
	ExitCode int
	// Stdout contains standard output
	Stdout string
	// Stderr contains standard error output
	Stderr string
	// Duration is how long the command took to execute
	Duration time.Duration
	// Success indicates if the command succeeded (exit code 0)
	Success bool
}

ExecuteResult represents the result of command execution

type FileInfo ΒΆ

type FileInfo struct {
	Name    string
	Path    string
	Size    int64
	Mode    os.FileMode
	ModTime time.Time
	IsDir   bool
}

FileInfo represents file or directory information

type FilesystemProvider ΒΆ

type FilesystemProvider interface {
	// Exists checks if a file or directory exists
	Exists(path string) bool
	// IsDirectory checks if path is a directory
	IsDirectory(path string) bool
	// IsFile checks if path is a regular file
	IsFile(path string) bool
	// ReadFile reads entire file contents
	ReadFile(path string) ([]byte, error)
	// WriteFile writes data to a file
	WriteFile(path string, data []byte, perm os.FileMode) error
	// AppendToFile appends data to a file
	AppendToFile(path string, data []byte) error
	// CreateDirectory creates a directory and any necessary parent directories
	CreateDirectory(path string, perm os.FileMode) error
	// RemoveFile removes a file
	RemoveFile(path string) error
	// RemoveDirectory removes a directory and all its contents
	RemoveDirectory(path string) error
	// CopyFile copies a file from source to destination
	CopyFile(src, dst string) error
	// MoveFile moves/renames a file
	MoveFile(src, dst string) error
	// ListDirectory lists files and directories in a path
	ListDirectory(path string) ([]FileInfo, error)
	// GetPermissions returns file permissions
	GetPermissions(path string) (os.FileMode, error)
	// SetPermissions sets file permissions
	SetPermissions(path string, perm os.FileMode) error
	// CreateSymlink creates a symbolic link
	CreateSymlink(oldPath, newPath string) error
	// ReadSymlink reads the target of a symbolic link
	ReadSymlink(path string) (string, error)
	// GetFileInfo returns detailed file information
	GetFileInfo(path string) (FileInfo, error)
	// TempDirectory creates a temporary directory
	TempDirectory(prefix string) (string, error)
	// ExpandPath expands ~ and environment variables in paths
	ExpandPath(path string) string
	// OpenFile opens a file with specified flags
	OpenFile(path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error)
}

FilesystemProvider handles file and directory operations.

type GitProvider ΒΆ

type GitProvider interface {
	// Clone clones or updates per spec. The mode's data-safety contract above
	// is binding on every implementation.
	Clone(spec CloneSpec) (CloneResult, error)
	// Checkout checks out an exact SHA (forced; doctor inspects the version
	// that was applied).
	Checkout(spec CheckoutSpec) error

	// LocalSHA returns the HEAD SHA of the repository at path.
	// ("", nil) = unknown, not error.
	LocalSHA(path RepoPath) (SHA, error)
	// RemoteSHA returns the remote HEAD (or branch tip) SHA for id.
	// Branch "" resolves the remote default via symref.
	RemoteSHA(id RepoID) (SHA, error)
	// BareSHA returns the SHA a bare-layout clone at path is synced to, for
	// the given branch ("" for the remote default).
	BareSHA(path RepoPath, branch string) (SHA, error)
	// StorageSHA returns the HEAD SHA of the clean storage copy of id, or
	// ("", nil) when no clean storage exists.
	StorageSHA(id RepoID) (SHA, error)

	// Layout classifies what sits at path.
	Layout(path RepoPath) RepoLayout
}

GitProvider is the one seam for all git operations. Handlers, engine, doctor, renderer, and parser program against it; the implementation behind it (go-git v5 + system-git fallbacks today, go-git v6 after #032) is a swappable detail.

Error contract:

  • Hard failures (clone/fetch failure, checkout conflict) are errors.
  • Recoverable incidents are CloneResult.Notes, never errors.
  • An unknown SHA is ("", nil), not an error.

Data-safety contract per CloneMode:

  • ModeTwoStage: the target is a content mirror (no .git); re-cloning replaces content but the clean storage is the source of truth.
  • ModeDirect: updating an existing copy reverts tracked changes, but untracked files MUST survive (#031).
  • ModeBare: updates never touch existing worktrees registered under <path>/.

type HTTPClient ΒΆ

type HTTPClient interface {
	// Get performs an HTTP GET request
	Get(url string) (*http.Response, error)
	// Post performs an HTTP POST request
	Post(url, contentType string, body io.Reader) (*http.Response, error)
	// Do performs an HTTP request
	Do(req *http.Request) (*http.Response, error)
}

HTTPClient provides HTTP request capabilities

type NetworkProvider ΒΆ

type NetworkProvider interface {
	// HTTPClient returns an HTTP client for making requests
	HTTPClient() HTTPClient
	// DownloadFile downloads a file from URL to local path
	DownloadFile(url, path string) error
	// GetURLContent fetches content from a URL
	GetURLContent(url string) ([]byte, error)
	// IsReachable checks if a host is reachable
	IsReachable(host string, port int, timeout time.Duration) bool
}

NetworkProvider handles network operations.

type OSDetector ΒΆ

type OSDetector interface {
	// Name returns the normalized OS name (mac, linux, windows)
	Name() string
	// Architecture returns the system architecture (amd64, arm64, etc.)
	Architecture() string
	// IsRoot returns true if running with root/admin privileges
	IsRoot() bool
	// CurrentUser returns information about the current user
	CurrentUser() (UserInfo, error)
}

OSDetector provides operating system detection capabilities.

type PackageManagerProvider ΒΆ

type PackageManagerProvider interface {
	// Install installs packages using the appropriate package manager
	Install(packages []string, manager string) (*ExecuteResult, error)
	// Uninstall removes packages using the appropriate package manager
	Uninstall(packages []string, manager string) (*ExecuteResult, error)
	// IsInstalled checks if a package is installed
	IsInstalled(packageName, manager string) bool
	// GetInstalledVersion returns the installed version of a package
	GetInstalledVersion(packageName, manager string) (string, error)
	// IsManagerAvailable checks if a package manager is available
	IsManagerAvailable(manager string) bool
	// GetDefaultManager returns the default package manager for the current OS
	GetDefaultManager() string
}

PackageManagerProvider handles package manager operations. This abstracts different package managers (brew, apt, snap, etc.) behind a common interface.

type ProcessExecutor ΒΆ

type ProcessExecutor interface {
	// Execute runs a command and returns the result
	Execute(cmd string, options ExecuteOptions) (*ExecuteResult, error)
	// ExecuteWithContext runs a command with context/timeout
	ExecuteWithContext(cmd string, options ExecuteOptions, timeout time.Duration) (*ExecuteResult, error)
	// IsCommandAvailable checks if a command exists in PATH
	IsCommandAvailable(cmd string) bool
	// GetEnvironmentVar returns an environment variable value
	GetEnvironmentVar(key string) string
	// SetEnvironmentVar sets an environment variable for child processes
	SetEnvironmentVar(key, value string) error
}

ProcessExecutor handles command execution and process management.

type RepoID ΒΆ added in v0.56.3

type RepoID struct {
	URL    string
	Branch string
}

RepoID identifies a remote: URL in any accepted form (@github:, ssh, https) + optional branch. Struct because the pair already repeats across three methods; new fields (e.g. auth hints) won't touch signatures.

type RepoLayout ΒΆ added in v0.56.3

type RepoLayout uint8

RepoLayout classifies what sits at a path.

const (
	LayoutNone     RepoLayout = iota // not a repository
	LayoutStandard                   // working copy with a .git directory
	LayoutBare                       // bare repository (path itself, or <path>/.git in the bare-clone layout)
	LayoutWorktree                   // linked worktree / gitfile (.git is a regular file)
)

type RepoPath ΒΆ added in v0.56.3

type RepoPath string

RepoPath is a working-copy or bare-layout repository path (~ allowed; expansion belongs to the engine below this seam).

type SHA ΒΆ added in v0.56.3

type SHA string

SHA is a git commit hash.

type SystemProvider ΒΆ

type SystemProvider interface {
	OS() OSDetector
	Process() ProcessExecutor
	Filesystem() FilesystemProvider
	Network() NetworkProvider
}

SystemProvider is the main interface that combines all platform operations. It serves as the entry point for all system interactions.

type TestContainer ΒΆ

type TestContainer struct {
	// contains filtered or unexported fields
}

TestContainer provides a fluent interface for configuring test containers. It allows easy setup of mocks and test doubles for unit testing.

func NewTestContainer ΒΆ

func NewTestContainer() *TestContainer

NewTestContainer creates a new test container that can be configured with mocks. All providers are initially nil and must be set via the configuration functions.

func (*TestContainer) Build ΒΆ

func (tc *TestContainer) Build() Container

Build returns the configured Container.

func (*TestContainer) WithCryptoProvider ΒΆ

func (tc *TestContainer) WithCryptoProvider(provider CryptoProvider) *TestContainer

WithCryptoProvider configures the test container to use a specific crypto provider.

func (*TestContainer) WithGitProvider ΒΆ

func (tc *TestContainer) WithGitProvider(provider GitProvider) *TestContainer

WithGitProvider configures the test container to use a specific git provider.

func (*TestContainer) WithPackageManagerProvider ΒΆ

func (tc *TestContainer) WithPackageManagerProvider(provider PackageManagerProvider) *TestContainer

WithPackageManagerProvider configures the test container to use a specific package manager provider.

func (*TestContainer) WithSystemProvider ΒΆ

func (tc *TestContainer) WithSystemProvider(provider SystemProvider) *TestContainer

WithSystemProvider configures the test container to use a specific system provider.

type UserInfo ΒΆ

type UserInfo struct {
	Username string
	UID      string
	GID      string
	HomeDir  string
}

UserInfo represents user information

Directories ΒΆ

Path Synopsis
Package mocks provides mock implementations of platform interfaces for testing.
Package mocks provides mock implementations of platform interfaces for testing.
Package testutils provides utilities for testing Blueprint handlers and components.
Package testutils provides utilities for testing Blueprint handlers and components.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL