Merge pull request #5 from Snider/feature-increase-test-coverage
Feature increase test coverage
This commit is contained in:
commit
71efcb71e2
11 changed files with 245 additions and 79 deletions
|
|
@ -1,47 +1,156 @@
|
|||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetMiner tests the GetMiner function
|
||||
func TestGetMiner(t *testing.T) {
|
||||
m := setupTestManager(t)
|
||||
defer m.Stop()
|
||||
|
||||
config := &Config{
|
||||
HTTPPort: 9003,
|
||||
Pool: "test:1234",
|
||||
Wallet: "testwallet",
|
||||
}
|
||||
|
||||
// Case 1: Get an existing miner
|
||||
startedMiner, _ := m.StartMiner("xmrig", config)
|
||||
retrievedMiner, err := m.GetMiner(startedMiner.GetName())
|
||||
if err != nil {
|
||||
t.Fatalf("Expected to get miner, but got error: %v", err)
|
||||
}
|
||||
if retrievedMiner.GetName() != startedMiner.GetName() {
|
||||
t.Errorf("Expected to get miner %s, but got %s", startedMiner.GetName(), retrievedMiner.GetName())
|
||||
}
|
||||
|
||||
// Case 2: Attempt to get a non-existent miner
|
||||
_, err = m.GetMiner("nonexistent")
|
||||
if err == nil {
|
||||
t.Error("Expected an error when getting a non-existent miner, but got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestListMiners tests the ListMiners function
|
||||
func TestListMiners(t *testing.T) {
|
||||
m := setupTestManager(t)
|
||||
defer m.Stop()
|
||||
|
||||
// Case 1: List miners when empty
|
||||
miners := m.ListMiners()
|
||||
if len(miners) != 0 {
|
||||
t.Errorf("Expected 0 miners, but got %d", len(miners))
|
||||
}
|
||||
|
||||
// Case 2: List miners when not empty
|
||||
config := &Config{
|
||||
HTTPPort: 9004,
|
||||
Pool: "test:1234",
|
||||
Wallet: "testwallet",
|
||||
}
|
||||
_, _ = m.StartMiner("xmrig", config)
|
||||
miners = m.ListMiners()
|
||||
if len(miners) != 1 {
|
||||
t.Errorf("Expected 1 miner, but got %d", len(miners))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ func (s *Service) setupRoutes() {
|
|||
|
||||
// New route to serve the custom HTML element bundle
|
||||
// This path now points to the output of the Angular project within the 'ui' directory
|
||||
s.Router.StaticFile("/component/mining-dashboard.js", "./ui/dist/ui/main.js")
|
||||
s.Router.StaticFile("/component/mining-dashboard.js", "./ui/dist/ui/mbe-mining-dashboard.js")
|
||||
|
||||
// Register Swagger UI route under a distinct sub-path to avoid conflicts
|
||||
swaggerURL := ginSwagger.URL(fmt.Sprintf("http://%s%s/doc.json", s.DisplayAddr, s.SwaggerUIPath))
|
||||
|
|
|
|||
|
|
@ -92,6 +92,35 @@ func TestHandleListMiners(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHandleGetInfo(t *testing.T) {
|
||||
router, _ := setupTestRouter()
|
||||
|
||||
// Case 1: Successful response
|
||||
req, _ := http.NewRequest("GET", "/info", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleDoctor(t *testing.T) {
|
||||
router, mockManager := setupTestRouter()
|
||||
mockManager.ListAvailableMinersFunc = func() []AvailableMiner {
|
||||
return []AvailableMiner{{Name: "xmrig"}}
|
||||
}
|
||||
|
||||
// Case 1: Successful response
|
||||
req, _ := http.NewRequest("POST", "/doctor", nil)
|
||||
w := httptest.NewRecorder()
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected status %d, got %d", http.StatusOK, w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleStartMiner(t *testing.T) {
|
||||
router, mockManager := setupTestRouter()
|
||||
mockManager.StartMinerFunc = func(minerType string, config *Config) (Miner, error) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
15
server.log
Normal file
15
server.log
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
Tidying dependencies...
|
||||
go mod tidy
|
||||
go: downloading github.com/inconshreveable/mousetrap v1.1.0
|
||||
go: downloading github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0
|
||||
go: downloading github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55
|
||||
go: downloading github.com/tklauser/go-sysconf v0.3.15
|
||||
go: downloading github.com/stretchr/testify v1.11.1
|
||||
go: downloading github.com/gin-contrib/gzip v0.0.6
|
||||
go: downloading github.com/google/go-cmp v0.7.0
|
||||
go: downloading github.com/ebitengine/purego v0.9.0
|
||||
go: downloading github.com/yusufpapurcu/wmi v1.2.4
|
||||
go: downloading github.com/json-iterator/go v1.1.12
|
||||
go: downloading github.com/modern-go/reflect2 v1.0.2
|
||||
go: downloading github.com/bytedance/sonic v1.14.0
|
||||
go: downloading github.com/goccy/go-json v0.10.5
|
||||
6
server2.log
Normal file
6
server2.log
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Tidying dependencies...
|
||||
go mod tidy
|
||||
Generating Swagger documentation...
|
||||
swag init -g ./cmd/mining/main.go
|
||||
make: swag: No such file or directory
|
||||
make: *** [Makefile:93: docs] Error 127
|
||||
6
server3.log
Normal file
6
server3.log
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Tidying dependencies...
|
||||
go mod tidy
|
||||
Generating Swagger documentation...
|
||||
swag init -g ./cmd/mining/main.go
|
||||
make: swag: No such file or directory
|
||||
make: *** [Makefile:93: docs] Error 127
|
||||
0
server4.log
Normal file
0
server4.log
Normal file
0
server5.log
Normal file
0
server5.log
Normal file
0
server6.log
Normal file
0
server6.log
Normal file
|
|
@ -4,7 +4,7 @@
|
|||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --output-path=dist && cat dist/{runtime,polyfills,main}.js > dist/mbe-mining-dashboard.js",
|
||||
"build": "ng build --output-path=dist/ui && cat dist/ui/runtime.js dist/ui/polyfills.js dist/ui/main.js > dist/ui/mbe-mining-dashboard.js",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue