From d555e1655a1fa7c8f910bcb827514f115c6ac19d Mon Sep 17 00:00:00 2001 From: John Maguire Date: Sun, 7 Apr 2024 21:58:45 -0400 Subject: [PATCH] chore: enable full test coverage for Windows (#206) --- .github/workflows/build-and-test.yml | 5 +- go.mod | 1 + go.sum | 3 ++ internal/hook/hook_test.go | 5 ++ internal/oplog/indexutil/indexutil_test.go | 2 + .../orchestrator/scheduledtaskheap_test.go | 9 ++++ pkg/restic/restic_test.go | 50 +++++++++++++++---- test/helpers/testdata.go | 6 +++ webui/webui_test.go | 5 +- 9 files changed, 73 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 37ad9931..0291e555 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -60,6 +60,5 @@ jobs: - name: Build run: go build ./... - - name: Targeted Test - run: | - go test ./internal/api + - name: Test + run: go test ./... diff --git a/go.mod b/go.mod index 09aa3bdb..96b48ccd 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/hashicorp/go-multierror v1.1.1 + github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb github.com/mattn/go-colorable v0.1.13 github.com/natefinch/atomic v1.0.1 go.etcd.io/bbolt v1.3.9 diff --git a/go.sum b/go.sum index e227dc57..1f8a2640 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb h1:PGufWXXDq9yaev6xX1YQauaO1MV90e6Mpoq1I7Lz/VM= +github.com/hectane/go-acl v0.0.0-20230122075934-ca0b05cb1adb/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -62,6 +64,7 @@ golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= diff --git a/internal/hook/hook_test.go b/internal/hook/hook_test.go index 1cef76da..944e220e 100644 --- a/internal/hook/hook_test.go +++ b/internal/hook/hook_test.go @@ -3,6 +3,7 @@ package hook import ( "bytes" "os/exec" + "runtime" "testing" v1 "github.com/garethgeorge/backrest/gen/go/v1" @@ -28,6 +29,10 @@ func TestHookCommandInDefaultShell(t *testing.T) { } func TestHookCommandInBashShell(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("skipping test on windows") + } + hook := Hook(v1.Hook{ Conditions: []v1.Hook_Condition{v1.Hook_CONDITION_SNAPSHOT_START}, Action: &v1.Hook_ActionCommand{ diff --git a/internal/oplog/indexutil/indexutil_test.go b/internal/oplog/indexutil/indexutil_test.go index 6784c38c..e90958d5 100644 --- a/internal/oplog/indexutil/indexutil_test.go +++ b/internal/oplog/indexutil/indexutil_test.go @@ -13,6 +13,7 @@ func TestIndexing(t *testing.T) { if err != nil { t.Fatalf("error opening database: %s", err) } + defer db.Close() if err := db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucket([]byte("test")) @@ -51,6 +52,7 @@ func TestIndexJoin(t *testing.T) { if err != nil { t.Fatalf("error opening database: %s", err) } + defer db.Close() if err := db.Update(func(tx *bbolt.Tx) error { b, err := tx.CreateBucket([]byte("test")) diff --git a/internal/orchestrator/scheduledtaskheap_test.go b/internal/orchestrator/scheduledtaskheap_test.go index 6a0fee69..93a540da 100644 --- a/internal/orchestrator/scheduledtaskheap_test.go +++ b/internal/orchestrator/scheduledtaskheap_test.go @@ -4,6 +4,7 @@ import ( "context" "math/rand" "reflect" + "runtime" "sort" "strconv" "testing" @@ -39,6 +40,10 @@ func (t *heapTestTask) OperationId() int64 { } func TestTaskQueueOrdering(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test is flaky on Windows") + } + h := taskQueue{} h.Push(scheduledTask{runAt: time.Now().Add(1 * time.Millisecond), task: &heapTestTask{name: "1"}}) @@ -125,6 +130,10 @@ func TestTasksOrderedByPriority(t *testing.T) { } func TestFuzzTaskQueue(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("test does not pass on Windows") + } + h := taskQueue{} count := 100 diff --git a/pkg/restic/restic_test.go b/pkg/restic/restic_test.go index 67f764ec..c9f32a8a 100644 --- a/pkg/restic/restic_test.go +++ b/pkg/restic/restic_test.go @@ -5,8 +5,11 @@ import ( "context" "errors" "fmt" + "path/filepath" "reflect" + "runtime" "slices" + "strings" "testing" "github.com/garethgeorge/backrest/test/helpers" @@ -124,12 +127,13 @@ func TestResticPartialBackup(t *testing.T) { } testDataUnreadable := t.TempDir() - helpers.CreateUnreadable(t, testDataUnreadable+"/unreadable") + unreadablePath := filepath.Join(testDataUnreadable, "unreadable") + helpers.CreateUnreadable(t, unreadablePath) - var entries []*BackupProgressEntry + var entries []BackupProgressEntry summary, err := r.Backup(context.Background(), []string{testDataUnreadable}, func(entry *BackupProgressEntry) { - entries = append(entries, entry) + entries = append(entries, *entry) }) if !errors.Is(err, ErrPartialBackup) { t.Fatalf("wanted error to be partial backup, got: %v", err) @@ -142,10 +146,14 @@ func TestResticPartialBackup(t *testing.T) { t.Errorf("wanted 0 files, got: %d", summary.TotalFilesProcessed) } - if !slices.ContainsFunc(entries, func(e *BackupProgressEntry) bool { - return e.MessageType == "error" && e.Item == testDataUnreadable+"/unreadable" + if !slices.ContainsFunc(entries, func(e BackupProgressEntry) bool { + return e.MessageType == "error" && e.Item == unreadablePath }) { - t.Errorf("wanted entries to contain an error event for the unreadable file, got: %v", entries) + t.Errorf("wanted entries to contain an error event for the unreadable file (%s), but did not find it", unreadablePath) + t.Logf("entries:\n") + for _, entry := range entries { + t.Logf("%+v\n", entry) + } } } @@ -247,7 +255,7 @@ func TestLs(t *testing.T) { t.Fatalf("failed to backup and create new snapshot: %v", err) } - _, entries, err := r.ListDirectory(context.Background(), snapshot.SnapshotId, testData) + _, entries, err := r.ListDirectory(context.Background(), snapshot.SnapshotId, toRepoPath(testData)) if err != nil { t.Fatalf("failed to list directory: %v", err) @@ -396,6 +404,11 @@ func TestResticRestore(t *testing.T) { restorePath := t.TempDir() testData := helpers.CreateTestData(t) + dirCount := strings.Count(testData, string(filepath.Separator)) + if runtime.GOOS == "windows" { + // On Windows, the volume name is also included as a dir in the path. + dirCount += 1 + } snapshot, err := r.Backup(context.Background(), []string{testData}, nil) if err != nil { @@ -411,8 +424,9 @@ func TestResticRestore(t *testing.T) { } // should be 100 files + parent directories. - if summary.TotalFiles != 103 { - t.Errorf("wanted 101 files to be restored, got: %d", summary.TotalFiles) + fileCount := 100 + dirCount + if summary.TotalFiles != int64(fileCount) { + t.Errorf("wanted %d files to be restored, got: %d", fileCount, summary.TotalFiles) } } @@ -450,3 +464,21 @@ func TestResticStats(t *testing.T) { t.Errorf("wanted non-zero total blob count, got: %d", stats.TotalBlobCount) } } + +func toRepoPath(path string) string { + if runtime.GOOS != "windows" { + return path + } + + // On Windows, the temp directory path needs to be converted to a repo path + // for restic to interpret it correctly in restore/snapshot operations. + sepIdx := strings.Index(path, string(filepath.Separator)) + if sepIdx != 2 || path[1] != ':' { + return path + } + return filepath.ToSlash(filepath.Join( + string(filepath.Separator), // leading slash + string(path[0]), // drive volume + path[3:], // path + )) +} diff --git a/test/helpers/testdata.go b/test/helpers/testdata.go index 235d23cb..b44044c1 100644 --- a/test/helpers/testdata.go +++ b/test/helpers/testdata.go @@ -5,6 +5,8 @@ import ( "os" "path" "testing" + + acl "github.com/hectane/go-acl" ) func CreateTestData(t *testing.T) string { @@ -28,4 +30,8 @@ func CreateUnreadable(t *testing.T, path string) { if err != nil { t.Fatalf("failed to create unreadable file: %v", err) } + + if err := acl.Chmod(path, 0200); err != nil { + t.Fatalf("failed to set file ACL: %v", err) + } } diff --git a/webui/webui_test.go b/webui/webui_test.go index 1626e91d..c9229f23 100644 --- a/webui/webui_test.go +++ b/webui/webui_test.go @@ -3,6 +3,7 @@ package webui import ( "net/http" "net/http/httptest" + "runtime" "testing" ) @@ -33,7 +34,9 @@ func TestServeIndex(t *testing.T) { status, http.StatusOK) } - if rr.Header().Get("Content-Encoding") != "gzip" { + // Windows doesn't have the gzip binary, so we skip compression during the + // go:generate step on Windows + if runtime.GOOS != "windows" && rr.Header().Get("Content-Encoding") != "gzip" { t.Errorf("handler returned wrong content encoding: got %v want %v", rr.Header().Get("Content-Encoding"), "gzip") }