mirror of
https://git.numenor-labs.us/dsfx.git
synced 2025-04-29 08:10:34 +00:00
220 lines
5.9 KiB
Go
220 lines
5.9 KiB
Go
package sim
|
||
|
||
import (
|
||
"errors"
|
||
"math/rand"
|
||
"sync"
|
||
"time"
|
||
|
||
"koti.casa/numenor-labs/dsfx/internal/lib/disk"
|
||
"koti.casa/numenor-labs/dsfx/internal/lib/system"
|
||
)
|
||
|
||
// SimSystem is a simulated implementation of system.System.
|
||
// It allows the caller to set parameters for latency and failure chance (from tol)
|
||
// and provides in–memory values for command–line arguments, environment variables,
|
||
// and standard output/error.
|
||
type SimSystem struct {
|
||
// Simulation tolerance parameters (latency, failure chance, corruption chance).
|
||
tol Tolerance
|
||
|
||
// Simulated command–line arguments.
|
||
args []string
|
||
|
||
// In–memory environment variables. Protected by mu.
|
||
mu sync.Mutex
|
||
env map[string]string
|
||
|
||
// Simulated directory paths.
|
||
homeDir string
|
||
configDir string
|
||
cacheDir string
|
||
tempDir string
|
||
|
||
// Simulated standard output and error.
|
||
stdout disk.File
|
||
stderr disk.File
|
||
}
|
||
|
||
// NewSimSystem returns a new simulated system that implements system.System.
|
||
// The caller provides a tolerance value and a slice of command–line arguments.
|
||
// Other fields (directories, environment) are pre–populated for simulation purposes.
|
||
func NewSimSystem(tol Tolerance, args []string) system.System {
|
||
s := &SimSystem{
|
||
tol: tol,
|
||
args: args,
|
||
env: make(map[string]string),
|
||
homeDir: "/home/simuser",
|
||
configDir: "/home/simuser/.config",
|
||
cacheDir: "/home/simuser/.cache",
|
||
tempDir: "/tmp",
|
||
}
|
||
// Create simulated stdout and stderr.
|
||
s.stdout = newSimOutput("stdout", tol)
|
||
s.stderr = newSimOutput("stderr", tol)
|
||
return s
|
||
}
|
||
|
||
// simulateOp applies the configured latency and possibly simulates an operation failure.
|
||
func (s *SimSystem) simulateOp() error {
|
||
time.Sleep(s.tol.Latency)
|
||
if rand.Float64() < s.tol.FailureChance {
|
||
return errors.New("simulated system operation failure")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// Args returns the simulated command–line arguments (skipping the program name).
|
||
func (s *SimSystem) Args() []string {
|
||
// Simulate latency even for argument access.
|
||
_ = s.simulateOp()
|
||
// Return a copy so that callers cannot modify the underlying slice.
|
||
cpy := make([]string, len(s.args))
|
||
copy(cpy, s.args)
|
||
return cpy
|
||
}
|
||
|
||
// Arg returns the simulated command–line argument at index i.
|
||
// If the index is out–of–range, it returns an empty string.
|
||
func (s *SimSystem) Arg(i int) string {
|
||
_ = s.simulateOp()
|
||
if i < 0 || i >= len(s.args) {
|
||
return ""
|
||
}
|
||
return s.args[i]
|
||
}
|
||
|
||
// UserHomeDir returns the simulated home directory.
|
||
func (s *SimSystem) UserHomeDir() (string, error) {
|
||
if err := s.simulateOp(); err != nil {
|
||
return "", err
|
||
}
|
||
return s.homeDir, nil
|
||
}
|
||
|
||
// UserConfigDir returns the simulated configuration directory.
|
||
func (s *SimSystem) UserConfigDir() (string, error) {
|
||
if err := s.simulateOp(); err != nil {
|
||
return "", err
|
||
}
|
||
return s.configDir, nil
|
||
}
|
||
|
||
// UserCacheDir returns the simulated cache directory.
|
||
func (s *SimSystem) UserCacheDir() (string, error) {
|
||
if err := s.simulateOp(); err != nil {
|
||
return "", err
|
||
}
|
||
return s.cacheDir, nil
|
||
}
|
||
|
||
// TempDir returns the simulated temporary directory.
|
||
func (s *SimSystem) TempDir() string {
|
||
// We simulate latency even though TempDir cannot fail.
|
||
_ = s.simulateOp()
|
||
return s.tempDir
|
||
}
|
||
|
||
// Stdout returns a simulated disk.File representing standard output.
|
||
func (s *SimSystem) Stdout() disk.File {
|
||
// In a real simulation you might add latency/failure to writes on stdout.
|
||
_ = s.simulateOp()
|
||
return s.stdout
|
||
}
|
||
|
||
// Stderr returns a simulated disk.File representing standard error.
|
||
func (s *SimSystem) Stderr() disk.File {
|
||
_ = s.simulateOp()
|
||
return s.stderr
|
||
}
|
||
|
||
// Exit simulates terminating the program with the given exit code.
|
||
// As with many tests, we simulate exit by panicing with a special error.
|
||
// This allows tests to catch the panic and inspect the exit code.
|
||
func (s *SimSystem) Exit(code int) {
|
||
_ = s.simulateOp()
|
||
panic(&SimExitError{Code: code})
|
||
}
|
||
|
||
// GetEnv retrieves the simulated value for the environment variable named by key.
|
||
func (s *SimSystem) GetEnv(key string) string {
|
||
_ = s.simulateOp()
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
return s.env[key]
|
||
}
|
||
|
||
// SetEnv sets the simulated environment variable named by key to value.
|
||
func (s *SimSystem) SetEnv(key, value string) error {
|
||
_ = s.simulateOp()
|
||
s.mu.Lock()
|
||
defer s.mu.Unlock()
|
||
s.env[key] = value
|
||
return nil
|
||
}
|
||
|
||
// SimExitError is the error value used to simulate a program exit.
|
||
type SimExitError struct {
|
||
Code int
|
||
}
|
||
|
||
// Error implements the error interface.
|
||
func (e *SimExitError) Error() string {
|
||
return "simulated program exit"
|
||
}
|
||
|
||
// --------------------------------------------------------------------------
|
||
// Helper functions and types for simulated standard output/error
|
||
// --------------------------------------------------------------------------
|
||
|
||
// newSimOutput creates a new simulated disk.File that acts as an output.
|
||
// Internally it creates a simulated file with an in–memory entry.
|
||
func newSimOutput(name string, tol Tolerance) disk.File {
|
||
entry := &simEntry{
|
||
name: name,
|
||
isDir: false,
|
||
perm: 0644,
|
||
modTime: time.Now(),
|
||
data: []byte{},
|
||
}
|
||
// Return a simFile which implements disk.File.
|
||
// (Note: simFile and simEntry are the types defined in dsfx/sim/disk.go.)
|
||
return &simFile{
|
||
entry: entry,
|
||
offset: 0,
|
||
readOnly: false,
|
||
tol: tol,
|
||
}
|
||
}
|
||
|
||
/*
|
||
Usage example:
|
||
|
||
// Define a tolerance for simulation.
|
||
tol := sim.Tolerance{
|
||
Latency: 10 * time.Millisecond,
|
||
FailureChance: 0.01,
|
||
CorruptionChance: 0.005,
|
||
}
|
||
|
||
// Simulate a system with custom command–line arguments.
|
||
sys := sim.NewSimSystem(tol, []string{"--verbose", "--config=sim.conf"})
|
||
|
||
// Calling system methods:
|
||
args := sys.Args()
|
||
home, err := sys.UserHomeDir()
|
||
// ... etc.
|
||
|
||
// To simulate an exit:
|
||
func run() {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
if exitErr, ok := r.(*sim.SimExitError); ok {
|
||
fmt.Printf("simulated exit(%d)\n", exitErr.Code)
|
||
}
|
||
}
|
||
}()
|
||
sys.Exit(3)
|
||
}
|
||
*/
|