Merge pull request #5 from Snider/feature-increase-test-coverage

Feature increase test coverage
This commit is contained in:
Snider 2025-11-13 19:50:29 +00:00 committed by GitHub
commit 71efcb71e2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 245 additions and 79 deletions

View file

@ -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))
}
}

View file

@ -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))

View file

@ -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) {

View file

@ -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
View 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
View 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
View 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
View file

0
server5.log Normal file
View file

0
server6.log Normal file
View file

View 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"
},