diff --git a/pkg/mining/manager_test.go b/pkg/mining/manager_test.go index 716898c..16dc08f 100644 --- a/pkg/mining/manager_test.go +++ b/pkg/mining/manager_test.go @@ -1,47 +1,104 @@ package mining import ( + "os" + "path/filepath" + "runtime" "testing" ) -// TestManager_StartStopMultipleMiners tests starting and stopping multiple miners. -func TestManager_StartStopMultipleMiners(t *testing.T) { - manager := NewManager() - defer manager.Stop() +// setupTestManager creates a new Manager and a dummy executable for tests. +// It also temporarily modifies the PATH to include the dummy executable's directory. +func setupTestManager(t *testing.T) *Manager { + dummyDir := t.TempDir() + executableName := "xmrig" + if runtime.GOOS == "windows" { + executableName += ".exe" + } + dummyPath := filepath.Join(dummyDir, executableName) - configs := []*Config{ - {Pool: "pool1", Wallet: "wallet1"}, + // Create a script that does nothing but exit, to simulate the miner executable + var script []byte + if runtime.GOOS == "windows" { + script = []byte("@echo off\r\nexit 0") + } else { + script = []byte("#!/bin/sh\nexit 0") } - minerNames := []string{"xmrig"} - - for i, config := range configs { - // Since we can't start a real miner in the test, we'll just check that the manager doesn't crash. - // A more complete test would involve a mock miner. - _, err := manager.StartMiner(minerNames[i], config) - if err == nil { - t.Errorf("Expected error when starting miner without executable") - } + if err := os.WriteFile(dummyPath, script, 0755); err != nil { + t.Fatalf("Failed to create dummy miner executable: %v", err) } + + // Prepend the dummy directory to the PATH + originalPath := os.Getenv("PATH") + t.Cleanup(func() { + os.Setenv("PATH", originalPath) + }) + os.Setenv("PATH", dummyDir+string(os.PathListSeparator)+originalPath) + + return NewManager() } -// TestManager_collectMinerStats tests the stat collection logic. -func TestManager_collectMinerStats(t *testing.T) { - manager := NewManager() - defer manager.Stop() +// TestStartMiner tests the StartMiner function +func TestStartMiner(t *testing.T) { + m := setupTestManager(t) + defer m.Stop() - // Since we can't start a real miner, we can't fully test this. - // A more complete test would involve a mock miner that can be added to the manager. - manager.collectMinerStats() -} + config := &Config{ + HTTPPort: 9001, // Use a different port to avoid conflict + Pool: "test:1234", + Wallet: "testwallet", + } -// TestManager_GetMinerHashrateHistory tests getting hashrate history. -func TestManager_GetMinerHashrateHistory(t *testing.T) { - manager := NewManager() - defer manager.Stop() + // Case 1: Successfully start a supported miner + miner, err := m.StartMiner("xmrig", config) + if err != nil { + t.Fatalf("Expected to start miner, but got error: %v", err) + } + if miner == nil { + t.Fatal("Expected miner to be non-nil, but it was") + } + if _, exists := m.miners[miner.GetName()]; !exists { + t.Errorf("Miner %s was not added to the manager's list", miner.GetName()) + } - _, err := manager.GetMinerHashrateHistory("non-existent") + // Case 2: Attempt to start an unsupported miner + _, err = m.StartMiner("unsupported", config) if err == nil { - t.Error("Expected error for getting hashrate history for non-existent miner") + t.Error("Expected an error when starting an unsupported miner, but got nil") + } + + // Case 3: Attempt to start a duplicate miner + _, err = m.StartMiner("xmrig", config) + if err == nil { + t.Error("Expected an error when starting a duplicate miner, but got nil") + } +} + +// TestStopMiner tests the StopMiner function +func TestStopMiner(t *testing.T) { + m := setupTestManager(t) + defer m.Stop() + + config := &Config{ + HTTPPort: 9002, + Pool: "test:1234", + Wallet: "testwallet", + } + + // Case 1: Stop a running miner + miner, _ := m.StartMiner("xmrig", config) + err := m.StopMiner(miner.GetName()) + if err != nil { + t.Fatalf("Expected to stop miner, but got error: %v", err) + } + if _, exists := m.miners[miner.GetName()]; exists { + t.Errorf("Miner %s was not removed from the manager's list", miner.GetName()) + } + + // Case 2: Attempt to stop a non-existent miner + err = m.StopMiner("nonexistent") + if err == nil { + t.Error("Expected an error when stopping a non-existent miner, but got nil") } } diff --git a/pkg/mining/xmrig.go b/pkg/mining/xmrig.go index 8ad85f0..9a524c6 100644 --- a/pkg/mining/xmrig.go +++ b/pkg/mining/xmrig.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "log" "net/http" "os" "os/exec" @@ -161,76 +162,76 @@ func (m *XMRigMiner) Uninstall() error { return os.RemoveAll(m.GetPath()) } -// CheckInstallation checks if the miner is installed and returns its details -func (m *XMRigMiner) CheckInstallation() (*InstallationDetails, error) { +// findMinerBinary searches for the miner executable, first in the standard installation path, +// then falls back to the system's PATH. +func (m *XMRigMiner) findMinerBinary() (string, error) { + executableName := "xmrig" + if runtime.GOOS == "windows" { + executableName += ".exe" + } + + // 1. Check the standard installation directory first baseInstallPath := m.GetPath() - details := &InstallationDetails{ - Path: baseInstallPath, // Initialize with base path, will be updated to versioned path - } - - if _, err := os.Stat(baseInstallPath); os.IsNotExist(err) { - details.IsInstalled = false - return details, nil - } - - // The directory exists, now check for the executable by finding the versioned sub-folder - files, err := os.ReadDir(baseInstallPath) - if err != nil { - return nil, fmt.Errorf("could not read installation directory: %w", err) - } - - var versionedDir string - for _, f := range files { - if f.IsDir() && strings.HasPrefix(f.Name(), "xmrig-") { - versionedDir = f.Name() - break + if _, err := os.Stat(baseInstallPath); err == nil { + files, err := os.ReadDir(baseInstallPath) + if err == nil { + for _, f := range files { + if f.IsDir() && strings.HasPrefix(f.Name(), "xmrig-") { + versionedPath := filepath.Join(baseInstallPath, f.Name()) + fullPath := filepath.Join(versionedPath, executableName) + if _, err := os.Stat(fullPath); err == nil { + log.Printf("Found miner binary at standard path: %s", fullPath) + return fullPath, nil + } + } + } } } - if versionedDir == "" { - details.IsInstalled = false // Directory exists but is empty or malformed - return details, nil + // 2. Fallback to searching the system PATH + path, err := exec.LookPath(executableName) + if err == nil { + log.Printf("Found miner binary in system PATH: %s", path) + return path, nil } - // Update the Path to be the versioned directory - details.Path = filepath.Join(baseInstallPath, versionedDir) + return "", errors.New("miner executable not found in standard directory or system PATH") +} - var executableName string - if runtime.GOOS == "windows" { - executableName = "xmrig.exe" - } else { - executableName = "xmrig" - } +// CheckInstallation checks if the miner is installed and returns its details +func (m *XMRigMiner) CheckInstallation() (*InstallationDetails, error) { + details := &InstallationDetails{} - executablePath := filepath.Join(details.Path, executableName) - if _, err := os.Stat(executablePath); os.IsNotExist(err) { - details.IsInstalled = false // Versioned folder exists, but no executable - return details, nil + binaryPath, err := m.findMinerBinary() + if err != nil { + details.IsInstalled = false + return details, nil // Return not-installed, but no error } details.IsInstalled = true - details.MinerBinary = executablePath // Set the full path to the miner binary + details.MinerBinary = binaryPath + details.Path = filepath.Dir(binaryPath) // The directory containing the executable // Try to get the version from the executable - cmd := exec.Command(executablePath, "--version") + cmd := exec.Command(binaryPath, "--version") var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err != nil { details.Version = "Unknown (could not run executable)" - return details, nil - } - - // XMRig version output is typically "XMRig 6.18.0" - fields := strings.Fields(out.String()) - if len(fields) >= 2 { - details.Version = fields[1] } else { - details.Version = "Unknown (could not parse version)" + // XMRig version output is typically "XMRig 6.18.0" + fields := strings.Fields(out.String()) + if len(fields) >= 2 { + details.Version = fields[1] + } else { + details.Version = "Unknown (could not parse version)" + } } - // Update the XMRigMiner struct's Path and MinerBinary fields + // Update the XMRigMiner struct's fields m.Path = details.Path m.MinerBinary = details.MinerBinary + m.Version = details.Version // Keep the miner's version in sync return details, nil }