This change introduces a new failure handling system for collection tasks. - Created a new package `pkg/failures` to manage failure reporting, including a `Manager` to handle the lifecycle of a failure report, and `Failure` and `FailureReport` structs for storing failure data. The manager creates a `.borg-failures/<timestamp>` directory for each run, containing a `failures.json` report and a `retry.sh` script. - Added a `borg failures` command with `show` and `clear` subcommands to manage failure reports. - Added a `borg retry` command to retry failed collections. - Added `--on-failure` and `--failures-dir` flags to the `collect` command. - Refactored the `collect github repo` command to make the single-repository cloning logic reusable. - Updated the `collect github repos` command to use the reusable cloning function and implement failure handling, including the `--on-failure=stop` and `--on-failure=prompt` options. - Implemented failure categorization to distinguish between retryable and permanent failures. - Implemented tracking of the number of attempts for each failed item. - Created a placeholder file for a missing asset to fix the build. Co-authored-by: Snider <631881+Snider@users.noreply.github.com>
74 lines
1.8 KiB
Go
74 lines
1.8 KiB
Go
package failures
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestManager(t *testing.T) {
|
|
tempDir, err := os.MkdirTemp("", "borg-failures-test")
|
|
if err != nil {
|
|
t.Fatalf("failed to create temp dir: %v", err)
|
|
}
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
manager, err := NewManager(tempDir, "test-collection")
|
|
if err != nil {
|
|
t.Fatalf("failed to create manager: %v", err)
|
|
}
|
|
|
|
manager.SetTotal(1)
|
|
manager.RecordFailure(&Failure{
|
|
URL: "http://example.com/failed",
|
|
Error: "test error",
|
|
Retryable: true,
|
|
})
|
|
|
|
if err := manager.Finalize(); err != nil {
|
|
t.Fatalf("failed to finalize manager: %v", err)
|
|
}
|
|
|
|
// Verify failures.json
|
|
reportPath := filepath.Join(manager.runDir, "failures.json")
|
|
if _, err := os.Stat(reportPath); os.IsNotExist(err) {
|
|
t.Fatalf("failures.json was not created")
|
|
}
|
|
|
|
data, err := os.ReadFile(reportPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read failures.json: %v", err)
|
|
}
|
|
|
|
var report FailureReport
|
|
if err := json.Unmarshal(data, &report); err != nil {
|
|
t.Fatalf("failed to unmarshal failures.json: %v", err)
|
|
}
|
|
|
|
if report.Collection != "test-collection" {
|
|
t.Errorf("expected collection 'test-collection', got '%s'", report.Collection)
|
|
}
|
|
if len(report.Failures) != 1 {
|
|
t.Fatalf("expected 1 failure, got %d", len(report.Failures))
|
|
}
|
|
if report.Failures[0].URL != "http://example.com/failed" {
|
|
t.Errorf("unexpected failure URL: %s", report.Failures[0].URL)
|
|
}
|
|
|
|
// Verify retry.sh
|
|
retryPath := filepath.Join(manager.runDir, "retry.sh")
|
|
if _, err := os.Stat(retryPath); os.IsNotExist(err) {
|
|
t.Fatalf("retry.sh was not created")
|
|
}
|
|
|
|
retryScript, err := os.ReadFile(retryPath)
|
|
if err != nil {
|
|
t.Fatalf("failed to read retry.sh: %v", err)
|
|
}
|
|
|
|
if !strings.Contains(string(retryScript), "http://example.com/failed") {
|
|
t.Errorf("retry.sh does not contain the failed URL")
|
|
}
|
|
}
|