Files
privatae-bastion/client-go/backuppush_bound_test.go
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

79 lines
2.2 KiB
Go

package main
// Gate: a push whose file stops answering (a network mount that died while
// the job reads it) does not stay pending: a read of the file that does not
// return within the bound fails the job with a sentence, whether it is stuck
// checking the digest or sending.
import (
"errors"
"os"
"path/filepath"
"strings"
"sync"
"testing"
"time"
)
// deadMountReads makes every read of a pushed file block until the test ends,
// then fail, as a read of a mount that went away does.
func deadMountReads(t *testing.T) {
t.Helper()
gate := make(chan struct{})
var once sync.Once
backupPushHookMu.Lock()
prev := backupPushRead
backupPushRead = func(f *os.File, p []byte) (int, error) {
<-gate
return 0, errors.New("the mount went away")
}
backupPushHookMu.Unlock()
t.Cleanup(func() {
once.Do(func() { close(gate) })
backupPushHookMu.Lock()
backupPushRead = prev
backupPushHookMu.Unlock()
})
}
func TestAPushFromAFolderThatStopsAnsweringFails(t *testing.T) {
allowLoopbackFetch(t)
backupPushHookMu.Lock()
prevStall := backupPushFileStall
backupPushFileStall = 200 * time.Millisecond
backupPushHookMu.Unlock()
t.Cleanup(func() { backupPushHookMu.Lock(); backupPushFileStall = prevStall; backupPushHookMu.Unlock() })
for _, phase := range []string{"checking the digest", "sending"} {
t.Run(phase, func(t *testing.T) {
root := t.TempDir()
_, sum := pushFixture(t, root, []byte(strings.Repeat("a backup ", 1000)))
abs := filepath.Join(root, fixtureData)
if phase == "sending" {
// The digest is known, so the first read is the upload's.
fi, err := os.Lstat(abs)
if err != nil {
t.Fatal(err)
}
noteHash(abs, fi, sum)
}
srv, _, _ := uploadServer(t, 200)
deadMountReads(t)
job := pushJob(root, sum)
done := make(chan struct{})
go func() { runBackupPush(job, srv.URL+"/put", abs); close(done) }()
select {
case <-done:
case <-time.After(5 * time.Second):
t.Fatal("a push whose file stopped answering is still running")
}
backupJobsMu.Lock()
state, msg := job.State, job.Error
backupJobsMu.Unlock()
if state != "failed" || !strings.Contains(msg, "stopped answering") {
t.Fatalf("the job reads %s %q", state, msg)
}
})
}
}