package sim import ( "errors" "math/rand" "sync" "time" "numenor-labs.us/dsfx/dsfx/internal/lib/disk" "numenor-labs.us/dsfx/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) } */