This commit is contained in:
Dev
2025-09-13 03:03:35 +03:00
parent 67b170415b
commit d3416687c9
6 changed files with 69 additions and 172 deletions

View File

@@ -16,7 +16,7 @@ all: test build ## Run tests and build binary
build: ## Build the binary for current platform build: ## Build the binary for current platform
@echo "🔨 Building $(BINARY_NAME) v$(VERSION)..." @echo "🔨 Building $(BINARY_NAME) v$(VERSION)..."
go build $(LDFLAGS) -o $(BINARY_NAME) . go build $(LDFLAGS) -o $(BINARY_NAME) .
@echo "Build complete: ./$(BINARY_NAME)" @echo "Build complete: ./$(BINARY_NAME)"
build-all: ## Build binaries for all platforms build-all: ## Build binaries for all platforms
@echo "🏗️ Building for all platforms..." @echo "🏗️ Building for all platforms..."
@@ -26,53 +26,53 @@ build-all: ## Build binaries for all platforms
GOOS=linux GOARCH=386 go build $(LDFLAGS) -o dist/$(BINARY_NAME)-linux-386 . GOOS=linux GOARCH=386 go build $(LDFLAGS) -o dist/$(BINARY_NAME)-linux-386 .
GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o dist/$(BINARY_NAME)-darwin-amd64 . GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o dist/$(BINARY_NAME)-darwin-amd64 .
GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o dist/$(BINARY_NAME)-darwin-arm64 . GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o dist/$(BINARY_NAME)-darwin-arm64 .
@echo "Multi-platform build complete in ./dist/" @echo "Multi-platform build complete in ./dist/"
test: ## Run tests test: ## Run tests
@echo "🧪 Running tests..." @echo "🧪 Running tests..."
go test -v ./... go test -v ./...
@echo "Tests complete" @echo "Tests complete"
test-coverage: ## Run tests with coverage test-coverage: ## Run tests with coverage
@echo "🧪 Running tests with coverage..." @echo "🧪 Running tests with coverage..."
go test -coverprofile=coverage.out ./... go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html go tool cover -html=coverage.out -o coverage.html
@echo "Coverage report generated: coverage.html" @echo "Coverage report generated: coverage.html"
fmt: ## Format code fmt: ## Format code
@echo "🎨 Formatting code..." @echo "Formatting code..."
go fmt ./... go fmt ./...
@echo "Code formatted" @echo "Code formatted"
lint: ## Run linters lint: ## Run linters
@echo "🔍 Running linters..." @echo "Running linters..."
@if command -v golangci-lint >/dev/null 2>&1; then \ @if command -v golangci-lint >/dev/null 2>&1; then \
golangci-lint run; \ golangci-lint run; \
else \ else \
echo "⚠️ golangci-lint not installed. Install with: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \ echo "golangci-lint not installed. Install with: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \
go vet ./...; \ go vet ./...; \
fi fi
@echo "Linting complete" @echo "Linting complete"
clean: ## Clean build artifacts clean: ## Clean build artifacts
@echo "🧹 Cleaning up..." @echo "Cleaning up..."
rm -f $(BINARY_NAME) rm -f $(BINARY_NAME)
rm -rf dist/ rm -rf dist/
rm -f coverage.out coverage.html rm -f coverage.out coverage.html
@echo "Clean complete" @echo "Clean complete"
install: build ## Install binary to /usr/local/bin install: build ## Install binary to /usr/local/bin
@echo "📦 Installing $(BINARY_NAME) to /usr/local/bin..." @echo "Installing $(BINARY_NAME) to /usr/local/bin..."
sudo cp $(BINARY_NAME) /usr/local/bin/ sudo cp $(BINARY_NAME) /usr/local/bin/
@echo "Installation complete" @echo "Installation complete"
uninstall: ## Remove binary from /usr/local/bin uninstall: ## Remove binary from /usr/local/bin
@echo "🗑️ Uninstalling $(BINARY_NAME)..." @echo "Uninstalling $(BINARY_NAME)..."
sudo rm -f /usr/local/bin/$(BINARY_NAME) sudo rm -f /usr/local/bin/$(BINARY_NAME)
@echo "Uninstallation complete" @echo "Uninstallation complete"
package: build-all ## Create distribution packages package: build-all ## Create distribution packages
@echo "📦 Creating packages..." @echo "Creating packages..."
@mkdir -p dist/packages @mkdir -p dist/packages
# Create tar.gz archives # Create tar.gz archives
@@ -84,7 +84,7 @@ package: build-all ## Create distribution packages
fi \ fi \
done done
@echo "Packages created in dist/packages/" @echo "Packages created in dist/packages/"
release: clean test package ## Prepare a release release: clean test package ## Prepare a release
@echo "🚀 Release $(VERSION) ready!" @echo "🚀 Release $(VERSION) ready!"
@@ -95,7 +95,7 @@ deps: ## Download dependencies
@echo "📥 Downloading dependencies..." @echo "📥 Downloading dependencies..."
go mod download go mod download
go mod tidy go mod tidy
@echo "Dependencies updated" @echo "Dependencies updated"
dev-deps: ## Install development dependencies dev-deps: ## Install development dependencies
@echo "🛠️ Installing development dependencies..." @echo "🛠️ Installing development dependencies..."
@@ -103,7 +103,7 @@ dev-deps: ## Install development dependencies
echo "Installing golangci-lint..."; \ echo "Installing golangci-lint..."; \
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \ go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest; \
fi fi
@echo "Development dependencies installed" @echo "Development dependencies installed"
run: build ## Build and run the application run: build ## Build and run the application
@echo "🏃 Running $(BINARY_NAME)..." @echo "🏃 Running $(BINARY_NAME)..."
@@ -114,7 +114,7 @@ demo: build ## Run a demo of the find command
./$(BINARY_NAME) find --top 3 ./$(BINARY_NAME) find --top 3
check: fmt lint test ## Run all checks (format, lint, test) check: fmt lint test ## Run all checks (format, lint, test)
@echo "All checks passed!" @echo "All checks passed!"
help: ## Show this help message help: ## Show this help message
@echo "FastestMirror Build System" @echo "FastestMirror Build System"

View File

@@ -1,99 +0,0 @@
# FastestMirror Project Summary
## Project Overview
FastestMirror is a professional Go application that automatically detects Linux distributions and finds the fastest package repository mirrors. It's designed to be both technically impressive and subtly entertaining - perfect for a resume project.
## Key Technical Features
### 🏗️ Architecture
- **Clean Architecture**: Organized with `cmd/`, `internal/`, and `pkg/` structure following Go best practices
- **Modular Design**: Separate packages for distribution detection, mirror testing, and configuration management
- **Concurrent Processing**: Uses Go goroutines for parallel mirror testing
- **Error Handling**: Robust error handling throughout with meaningful error messages
### 🔍 Distribution Detection
- Parses `/etc/os-release`, `/etc/lsb-release`, and fallback detection methods
- Supports major Linux families: Debian, Arch, Fedora, openSUSE, Gentoo, Alpine, etc.
- Graceful fallback mechanisms for edge cases
### ⚡ Mirror Testing Infrastructure
- Concurrent HTTP speed testing with configurable timeouts
- Latency measurement and download speed calculation
- Smart scoring algorithm combining multiple metrics
- Proper connection handling and resource cleanup
### 🎨 User Experience
- Beautiful CLI interface with colors and progress bars
- Professional help text with subtle humor
- Comprehensive command structure with subcommands
- Safe operations with backup and dry-run modes
### 🔧 Configuration Management
- Safe backup creation with timestamps
- Support for major package manager configurations
- Atomic operations with rollback capability
- Proper permission handling and root privilege checks
## Commands Implemented
### `fastestmirror find`
- Auto-detects distribution
- Tests mirrors concurrently
- Shows ranked results with performance metrics
- Configurable timeout and result count
### `fastestmirror apply`
- Requires appropriate privileges
- Creates automatic backups
- Applies fastest mirror safely
- Provides post-application instructions
### `fastestmirror version`
- Shows build information
- Displays Go version and architecture
- Links to repository
## Technical Highlights
### Dependencies
- **Cobra**: Professional CLI framework
- **Color Libraries**: Beautiful terminal output
- **Progress Bars**: Visual feedback during operations
- **Standard Library**: Extensive use of Go's robust standard library
### Code Quality
- Comprehensive error handling
- Clean separation of concerns
- Concurrent operations with proper synchronization
- Resource management and cleanup
### Safety Features
- Automatic configuration backups
- Dry-run mode for testing
- Root privilege validation
- Rollback capabilities on failure
## Subtle Humor Elements
The project includes professional yet entertaining elements:
- Witty error messages ("your connection might be fucked")
- Humorous descriptions in help text
- Satirical comments in code and documentation
- Professional tone with personality
## Resume Value
This project demonstrates:
- **Go Proficiency**: Modern Go idioms and best practices
- **Systems Programming**: Linux distribution detection and configuration management
- **Concurrent Programming**: Goroutines and proper synchronization
- **CLI Development**: Professional command-line interface design
- **Error Handling**: Robust error management and user feedback
- **Testing Infrastructure**: Network operations and performance measurement
- **Software Architecture**: Clean, modular design patterns
## Repository
- **Location**: `https://git.gostacks.org/iwasforcedtobehere/fastestmirror`
- **Author**: `@iwasforcedtobehere`
- **License**: MIT
- **Build System**: Comprehensive Makefile with cross-platform builds
This project showcases technical competence while maintaining personality - exactly what makes a great resume project stand out!

View File

@@ -4,14 +4,14 @@
FastestMirror is a blazingly fast, auto-magical tool that finds and configures the fastest package repository mirrors for your Linux distribution. No more suffering through downloads that move slower than continental drift! FastestMirror is a blazingly fast, auto-magical tool that finds and configures the fastest package repository mirrors for your Linux distribution. No more suffering through downloads that move slower than continental drift!
## Features That'll Make You Smile 😊 ## Features
- **🔍 Auto-Detection**: Automatically detects your Linux distribution (because we're not psychic, but we're pretty close) - **Auto-Detection**: Automatically detects your Linux distribution (because we're not psychic, but we're pretty close)
- **Concurrent Testing**: Tests multiple mirrors simultaneously using Go's goroutines (multithreading that actually works) - **Concurrent Testing**: Tests multiple mirrors simultaneously using Go's goroutines (multithreading that actually works)
- **🎨 Beautiful Output**: Colorful terminal interface that doesn't make your eyes bleed - **Beautiful Output**: Colorful terminal interface that doesn't make your eyes bleed
- **📊 Smart Scoring**: Combines speed and latency metrics to find the objectively best mirror - **Smart Scoring**: Combines speed and latency metrics to find the objectively best mirror
- **🔒 Safe Operations**: Always backs up your config before making changes (we're not monsters) - **Safe Operations**: Always backs up your config before making changes (we're not monsters)
- **🌍 Wide Support**: Works with major Linux distributions and their families - **Wide Support**: Works with major Linux distributions and their families
## Supported Distributions ## Supported Distributions
@@ -120,13 +120,13 @@ fastestmirror find --help
## Example Output ## Example Output
``` ```
🔍 Detecting your Linux distribution... Detecting your Linux distribution...
📦 Found: Ubuntu 22.04.3 LTS (debian family) Found: Ubuntu 22.04.3 LTS (debian family)
🔧 Loading mirrors for debian family... Loading mirrors for debian family...
Testing 8 mirrors (timeout: 10s)... Testing 8 mirrors (timeout: 10s)...
Testing mirrors ████████████████████████████████████████████████████ 8/8 Testing mirrors ████████████████████████████████████████████████████ 8/8
🎉 Testing complete! Here are your results: Testing complete! Here are your results:
Rank Mirror URL Latency Speed Score Rank Mirror URL Latency Speed Score
───────────────────────────────────────────────────────────────────────────────── ─────────────────────────────────────────────────────────────────────────────────
@@ -136,10 +136,10 @@ Rank Mirror URL Latency Speed Sco
#4 http://archive.ubuntu.com/ubuntu/ 234ms 6.2 MB/s 66.3 #4 http://archive.ubuntu.com/ubuntu/ 234ms 6.2 MB/s 66.3
#5 http://us.archive.ubuntu.com/ubuntu/ 312ms 4.1 MB/s 44.2 #5 http://us.archive.ubuntu.com/ubuntu/ 312ms 4.1 MB/s 44.2
🏆 Winner: http://mirror.kakao.com/ubuntu/ Winner: http://mirror.kakao.com/ubuntu/
This bad boy clocks in at 15.2 MB/s with 45ms latency This bad boy clocks in at 15.2 MB/s with 45ms latency
💡 To apply the fastest mirror, run: fastestmirror apply To apply the fastest mirror, run: fastestmirror apply
``` ```
## Configuration ## Configuration

View File

@@ -47,7 +47,7 @@ func runApply(cmd *cobra.Command, args []string) error {
} }
// Detect distribution // Detect distribution
fmt.Println("🔍 Detecting distribution...") fmt.Println("Detecting distribution...")
distroInfo, err := distro.DetectDistribution() distroInfo, err := distro.DetectDistribution()
if err != nil { if err != nil {
return fmt.Errorf("could not detect distribution: %w", err) return fmt.Errorf("could not detect distribution: %w", err)
@@ -73,7 +73,7 @@ func runApply(cmd *cobra.Command, args []string) error {
configManager := config.NewConfigManager(distroInfo) configManager := config.NewConfigManager(distroInfo)
// First, find the fastest mirror // First, find the fastest mirror
fmt.Printf("🔍 Finding fastest mirror for %s...\n", color.YellowString(distroInfo.Family)) fmt.Printf("Finding fastest mirror for %s...\n", color.YellowString(distroInfo.Family))
mirrorList := mirror.NewMirrorList(distroInfo.Family) mirrorList := mirror.NewMirrorList(distroInfo.Family)
@@ -95,12 +95,12 @@ func runApply(cmd *cobra.Command, args []string) error {
return fmt.Errorf("no working mirrors found - your connection might be fucked") return fmt.Errorf("no working mirrors found - your connection might be fucked")
} }
fmt.Printf("🏆 Best mirror found: %s\n", color.GreenString(bestMirror.URL)) fmt.Printf("Best mirror found: %s\n", color.GreenString(bestMirror.URL))
fmt.Printf("Speed: %.1f MB/s, Latency: %dms\n", bestMirror.Speed, bestMirror.Latency.Milliseconds()) fmt.Printf("Speed: %.1f MB/s, Latency: %dms\n", bestMirror.Speed, bestMirror.Latency.Milliseconds())
// Ask for confirmation unless forced // Ask for confirmation unless forced
if !force { if !force {
fmt.Printf("\n⚠️ This will modify %s\n", color.YellowString(configFile)) fmt.Printf("\nThis will modify %s\n", color.YellowString(configFile))
fmt.Print("Do you want to continue? [y/N]: ") fmt.Print("Do you want to continue? [y/N]: ")
var response string var response string
@@ -113,33 +113,33 @@ func runApply(cmd *cobra.Command, args []string) error {
} }
// Create backup // Create backup
fmt.Println("💾 Creating backup...") fmt.Println("Creating backup...")
backupPath, err := configManager.BackupConfig() backupPath, err := configManager.BackupConfig()
if err != nil { if err != nil {
return fmt.Errorf("failed to create backup: %w", err) return fmt.Errorf("failed to create backup: %w", err)
} }
fmt.Printf("Backup created: %s\n", color.GreenString(backupPath)) fmt.Printf("Backup created: %s\n", color.GreenString(backupPath))
// Apply the fastest mirror // Apply the fastest mirror
fmt.Println("🔧 Applying fastest mirror configuration...") fmt.Println("Applying fastest mirror configuration...")
err = configManager.ApplyMirror(bestMirror.URL) err = configManager.ApplyMirror(bestMirror.URL)
if err != nil { if err != nil {
// Try to restore backup on failure // Try to restore backup on failure
fmt.Printf("Failed to apply configuration: %v\n", err) fmt.Printf("Failed to apply configuration: %v\n", err)
fmt.Println("<22> Attempting to restore backup...") fmt.Println("<22> Attempting to restore backup...")
if restoreErr := configManager.RestoreBackup(backupPath); restoreErr != nil { if restoreErr := configManager.RestoreBackup(backupPath); restoreErr != nil {
return fmt.Errorf("configuration failed AND backup restore failed: %v (original error: %v)", restoreErr, err) return fmt.Errorf("configuration failed AND backup restore failed: %v (original error: %v)", restoreErr, err)
} }
fmt.Println("Backup restored successfully") fmt.Println("Backup restored successfully")
return fmt.Errorf("configuration application failed, backup restored: %w", err) return fmt.Errorf("configuration application failed, backup restored: %w", err)
} }
fmt.Printf("🎉 Successfully applied fastest mirror!\n") fmt.Printf("Successfully applied fastest mirror!\n")
fmt.Printf("🔗 New mirror: %s\n", color.GreenString(bestMirror.URL)) fmt.Printf("New mirror: %s\n", color.GreenString(bestMirror.URL))
fmt.Println() fmt.Println()
fmt.Printf("💡 Don't forget to run your package manager's update command:\n") fmt.Printf("Don't forget to run your package manager's update command:\n")
switch distroInfo.Family { switch distroInfo.Family {
case "debian": case "debian":

View File

@@ -33,21 +33,21 @@ This command will:
func runFind(cmd *cobra.Command, args []string) error { func runFind(cmd *cobra.Command, args []string) error {
// Detect distribution // Detect distribution
fmt.Println(color.CyanString("🔍 Detecting your Linux distribution...")) fmt.Println(color.CyanString("Detecting your Linux distribution..."))
distroInfo, err := distro.DetectDistribution() distroInfo, err := distro.DetectDistribution()
if err != nil { if err != nil {
return fmt.Errorf("fuck me, couldn't detect your distro: %w", err) return fmt.Errorf("fuck me, couldn't detect your distro: %w", err)
} }
fmt.Printf("📦 Found: %s\n", color.GreenString(distroInfo.String())) fmt.Printf("Found: %s\n", color.GreenString(distroInfo.String()))
if !distroInfo.IsSupported() { if !distroInfo.IsSupported() {
return fmt.Errorf("sorry, %s isn't supported yet - but hey, you're using something exotic! 🦄", distroInfo.ID) return fmt.Errorf("sorry, %s isn't supported yet - but hey, you're using something exotic! 🦄", distroInfo.ID)
} }
// Get mirrors for this distribution family // Get mirrors for this distribution family
fmt.Printf("🔧 Loading mirrors for %s family...\n", color.YellowString(distroInfo.Family)) fmt.Printf("Loading mirrors for %s family...\n", color.YellowString(distroInfo.Family))
mirrorList := mirror.NewMirrorList(distroInfo.Family) mirrorList := mirror.NewMirrorList(distroInfo.Family)
@@ -60,8 +60,7 @@ func runFind(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to load mirrors: %w", err) return fmt.Errorf("failed to load mirrors: %w", err)
} }
fmt.Printf("Testing %d mirrors (timeout: %ds each)...\n", fmt.Printf("Testing mirrors (timeout: %ds each)...\n", timeout)
len(mirrorList.Mirrors), timeout)
err = mirrorList.TestMirrors(ctx, time.Duration(timeout)*time.Second) err = mirrorList.TestMirrors(ctx, time.Duration(timeout)*time.Second)
@@ -70,7 +69,7 @@ func runFind(cmd *cobra.Command, args []string) error {
} }
// Display results // Display results
fmt.Println("\n" + color.GreenString("🎉 Testing complete! Here are your results:")) fmt.Println("\n" + color.GreenString("Testing complete! Here are your results:"))
fmt.Println() fmt.Println()
topMirrors := mirrorList.GetTop(topCount) topMirrors := mirrorList.GetTop(topCount)
@@ -85,11 +84,11 @@ func runFind(cmd *cobra.Command, args []string) error {
} }
if verbose { if verbose {
fmt.Printf("📊 Test Summary: %d/%d mirrors responded successfully\n", successCount, len(allMirrors)) fmt.Printf("Test Summary: %d/%d mirrors responded successfully\n", successCount, len(allMirrors))
fmt.Println() fmt.Println()
} }
if len(topMirrors) == 0 { if len(topMirrors) == 0 {
fmt.Println(color.RedString("😱 No working mirrors found. Your internet might be fucked, or all mirrors are down.")) fmt.Println(color.RedString("No working mirrors found. Your internet might be fucked, or all mirrors are down."))
return nil return nil
} }
@@ -132,32 +131,29 @@ func runFind(cmd *cobra.Command, args []string) error {
fmt.Println() fmt.Println()
best := mirrorList.GetBest() best := mirrorList.GetBest()
if best != nil { if best != nil {
fmt.Printf("🏆 Winner: %s\n", color.GreenString(best.URL)) fmt.Printf("Winner: %s\n", color.GreenString(best.URL))
fmt.Printf("This bad boy clocks in at %.1f MB/s with %dms latency\n", fmt.Printf("This bad boy clocks in at %.1f MB/s with %dms latency\n",
best.Speed, best.Latency.Milliseconds()) best.Speed, best.Latency.Milliseconds())
} }
// Show failed mirrors in verbose mode // Show failed mirror count in verbose mode (no detailed errors)
if verbose { if verbose {
failedMirrors := make([]mirror.Mirror, 0) failedCount := 0
for _, m := range allMirrors { for _, m := range allMirrors {
if m.Error != nil { if m.Error != nil {
failedMirrors = append(failedMirrors, m) failedCount++
} }
} }
if len(failedMirrors) > 0 { if failedCount > 0 {
fmt.Println() fmt.Println()
fmt.Printf("Failed mirrors (%d):\n", len(failedMirrors)) fmt.Printf("Failed mirrors: %d (errors hidden for cleaner output)\n", failedCount)
for _, m := range failedMirrors {
fmt.Printf(" %s - %s\n", color.RedString(m.URL), m.Error.Error())
}
} }
} }
fmt.Println() fmt.Println()
fmt.Printf("💡 To apply the fastest mirror, run: %s\n", fmt.Printf("To apply the fastest mirror, run: %s\n",
color.YellowString("fastestmirror apply")) color.CyanString("fastestmirror apply"))
return nil return nil
} }

View File

@@ -80,11 +80,11 @@ func NewMirrorList(family string) *MirrorList {
func (ml *MirrorList) LoadMirrors(ctx context.Context, family string) error { func (ml *MirrorList) LoadMirrors(ctx context.Context, family string) error {
fetcher := NewMirrorFetcher() fetcher := NewMirrorFetcher()
fmt.Printf("🌐 Fetching complete mirror list for %s...\n", family) fmt.Printf("Fetching complete mirror list for %s...\n", family)
mirrors, err := fetcher.FetchMirrors(ctx, family) mirrors, err := fetcher.FetchMirrors(ctx, family)
if err != nil { if err != nil {
fmt.Printf("⚠️ Failed to fetch mirrors online, using fallback list: %v\n", err) fmt.Printf("Failed to fetch mirrors online, using fallback list: %v\n", err)
mirrors = fetcher.getDefaultMirrors(family) mirrors = fetcher.getDefaultMirrors(family)
} }
@@ -102,7 +102,7 @@ func (ml *MirrorList) LoadMirrors(ctx context.Context, family string) error {
}) })
} }
fmt.Printf("📡 Loaded %d mirrors for testing\n", len(ml.Mirrors)) fmt.Printf("Loaded %d mirrors for testing\n", len(ml.Mirrors))
return nil return nil
} }
@@ -118,7 +118,7 @@ func (ml *MirrorList) TestMirrors(ctx context.Context, timeout time.Duration) er
results := make(chan TestResult, len(ml.Mirrors)) results := make(chan TestResult, len(ml.Mirrors))
var wg sync.WaitGroup var wg sync.WaitGroup
fmt.Printf("🔄 Testing %d mirrors concurrently...\n", len(ml.Mirrors)) fmt.Printf("Testing %d mirrors concurrently...\n", len(ml.Mirrors))
// Test each mirror concurrently // Test each mirror concurrently
for i := range ml.Mirrors { for i := range ml.Mirrors {
@@ -154,7 +154,7 @@ func (ml *MirrorList) TestMirrors(ctx context.Context, timeout time.Duration) er
} }
} }
fmt.Printf("Testing complete: %d successful, %d failed\n", successCount, failureCount) fmt.Printf("Testing complete: %d successful, %d failed\n", successCount, failureCount)
// Sort by score (higher is better), but put failed mirrors at the end // Sort by score (higher is better), but put failed mirrors at the end
sort.Slice(ml.Mirrors, func(i, j int) bool { sort.Slice(ml.Mirrors, func(i, j int) bool {