2025-03-21 22:51:04 -04:00
|
|
|
|
package sim
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"errors"
|
|
|
|
|
"math/rand"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
|
2025-03-25 15:24:26 -04:00
|
|
|
|
"numenor-labs.us/dsfx/dsfx/internal/lib/disk"
|
|
|
|
|
"numenor-labs.us/dsfx/dsfx/internal/lib/system"
|
2025-03-21 22:51:04 -04:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
*/
|