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)
|
|||
|
}
|
|||
|
*/
|