feat: bootstrap durable server store
This commit is contained in:
+65
-7
@@ -16,6 +16,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -26,6 +27,7 @@ import (
|
||||
const (
|
||||
manifestVersion = 1
|
||||
repositoryID = "rvbox"
|
||||
maxSuiteLogSize = 1 << 20
|
||||
)
|
||||
|
||||
var runIDPattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-]{0,63}$`)
|
||||
@@ -124,8 +126,8 @@ func (h *harness) integration(ctx context.Context, args []string) error {
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if *suite != "sample" {
|
||||
return fmt.Errorf("suite %q is not implemented yet; available: sample", *suite)
|
||||
if *suite != "sample" && *suite != "store" {
|
||||
return fmt.Errorf("suite %q is not implemented yet; available: sample, store", *suite)
|
||||
}
|
||||
|
||||
var current *manifest
|
||||
@@ -141,7 +143,7 @@ func (h *harness) integration(ctx context.Context, args []string) error {
|
||||
if current.Layer != "integration" || current.Suite != *suite {
|
||||
return errors.New("run layer/suite does not match resume request")
|
||||
}
|
||||
if current.Phase != "ready" && current.Phase != "interrupted" && current.Phase != "stopped" && current.Phase != "running" {
|
||||
if current.Phase != "ready" && current.Phase != "interrupted" && current.Phase != "stopped" && current.Phase != "running" && current.Phase != "failed" {
|
||||
return fmt.Errorf("run in phase %q is not resumable; recover or reuse it first", current.Phase)
|
||||
}
|
||||
} else {
|
||||
@@ -151,7 +153,7 @@ func (h *harness) integration(ctx context.Context, args []string) error {
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(h.out, current.RunID)
|
||||
if err := h.transition(current, "running", "sample-start", "sample integration run started"); err != nil {
|
||||
if err := h.transition(current, "running", *suite+"-start", *suite+" integration run started"); err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
@@ -160,10 +162,38 @@ func (h *harness) integration(ctx context.Context, args []string) error {
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
if err := h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: "invariant", Status: "passed", Detail: "manifest ownership and journal durability verified"}); err != nil {
|
||||
if *suite == "sample" {
|
||||
if err := h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: "invariant", Status: "passed", Detail: "manifest ownership and journal durability verified"}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err := h.runStoreSuite(ctx, current); err != nil {
|
||||
_ = h.transition(current, "failed", "store-failed", err.Error())
|
||||
return err
|
||||
}
|
||||
return h.transition(current, "completed", "sample-complete", "sample integration run completed")
|
||||
return h.transition(current, "completed", *suite+"-complete", *suite+" integration run completed")
|
||||
}
|
||||
|
||||
func (h *harness) runStoreSuite(ctx context.Context, current *manifest) error {
|
||||
if err := h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: "store-real-sqlite", Status: "running", Detail: "running real SQLite/WAL and filesystem cases"}); err != nil {
|
||||
return err
|
||||
}
|
||||
artifactDir := filepath.Join(h.runDir(current.RunID), "artifacts")
|
||||
if err := os.MkdirAll(artifactDir, 0o700); err != nil {
|
||||
return err
|
||||
}
|
||||
capture := &limitedCapture{limit: maxSuiteLogSize}
|
||||
command := exec.CommandContext(ctx, "go", "test", "-count=1", "-tags=integration", "-shuffle="+strconv.FormatInt(current.Seed, 10), "-timeout=2m", "./test/integration/store")
|
||||
command.Stdout = capture
|
||||
command.Stderr = capture
|
||||
err := command.Run()
|
||||
logPath := filepath.Join(artifactDir, "suite.log")
|
||||
if writeErr := atomicWrite(logPath, capture.Bytes(), 0o600); writeErr != nil {
|
||||
return writeErr
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("store suite failed (bounded log %s): %w", logPath, err)
|
||||
}
|
||||
return h.appendJournal(current.RunID, journalEntry{At: h.now(), Step: "store-real-sqlite", Status: "passed", Detail: "real SQLite/WAL and filesystem cases passed"})
|
||||
}
|
||||
|
||||
func (h *harness) environmentCommand(command string, args []string) error {
|
||||
@@ -195,7 +225,7 @@ func (h *harness) environmentCommand(command string, args []string) error {
|
||||
case "collect":
|
||||
return h.collect(current)
|
||||
case "recover":
|
||||
if current.Phase != "interrupted" && current.Phase != "stopped" && current.Phase != "running" {
|
||||
if current.Phase != "interrupted" && current.Phase != "stopped" && current.Phase != "running" && current.Phase != "failed" {
|
||||
return fmt.Errorf("run in phase %q does not need recovery", current.Phase)
|
||||
}
|
||||
return h.transition(current, "ready", "recover", "run recovered and ready to resume")
|
||||
@@ -215,6 +245,34 @@ func (h *harness) environmentCommand(command string, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
type limitedCapture struct {
|
||||
data []byte
|
||||
limit int
|
||||
truncated bool
|
||||
}
|
||||
|
||||
func (capture *limitedCapture) Write(data []byte) (int, error) {
|
||||
written := len(data)
|
||||
remaining := capture.limit - len(capture.data)
|
||||
if remaining > 0 {
|
||||
if len(data) > remaining {
|
||||
data = data[:remaining]
|
||||
}
|
||||
capture.data = append(capture.data, data...)
|
||||
}
|
||||
if written > remaining {
|
||||
capture.truncated = true
|
||||
}
|
||||
return written, nil
|
||||
}
|
||||
|
||||
func (capture *limitedCapture) Bytes() []byte {
|
||||
if !capture.truncated {
|
||||
return capture.data
|
||||
}
|
||||
return append(append([]byte(nil), capture.data...), []byte("\n[output truncated by RVBox test harness]\n")...)
|
||||
}
|
||||
|
||||
func (h *harness) create(requestedID, layer, suite string) (*manifest, error) {
|
||||
if requestedID == "" {
|
||||
id, err := domain.NewUUIDv7()
|
||||
|
||||
@@ -112,6 +112,34 @@ func TestIntegrationResumeValidation_HP_CFG_01(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoundedSuiteLogAndFailedRunRecovery_BH_STORE_03(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
capture := &limitedCapture{limit: 4}
|
||||
if written, err := capture.Write([]byte("123456")); err != nil || written != 6 {
|
||||
t.Fatalf("Write = (%d, %v)", written, err)
|
||||
}
|
||||
if got := string(capture.Bytes()); !strings.HasPrefix(got, "1234\n") || !strings.Contains(got, "truncated") {
|
||||
t.Fatalf("bounded output = %q", got)
|
||||
}
|
||||
|
||||
h := &harness{root: t.TempDir(), now: time.Now, out: &bytes.Buffer{}}
|
||||
current, err := h.create("failed-store", "integration", "store")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := h.transition(current, "failed", "store-failed", "injected"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := h.environmentCommand("recover", []string{"--run-id", current.RunID}); err != nil {
|
||||
t.Fatalf("recover failed run: %v", err)
|
||||
}
|
||||
loaded, err := h.load(current.RunID)
|
||||
if err != nil || loaded.Phase != "ready" {
|
||||
t.Fatalf("recovered run = (%+v, %v)", loaded, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCoverageInventoryReferencesExistingTests_HP_CFG_01(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user