//go:build linux && rootrunner package host import ( "errors" "fmt" "testing" "github.com/carlosframework/platform/internal/moves" "github.com/carlosframework/platform/internal/rootstate" ) func TestPlan14RootFloorSyscallCrashOrdering(t *testing.T) { testRootProtocolFloorCrashes(t, rootFloorDir) } func TestPlan14RootSeedsFloorSyscallCrashOrdering(t *testing.T) { testRootProtocolFloorCrashes(t, rootSeedsFloorDir) } func testRootProtocolFloorCrashes(t *testing.T, floorDir func(string) string) { for _, stage := range []string{"write", "chmod", "file-sync", "close", "rename", "parent-sync"} { for _, after := range []bool{false, true} { for cut := 1; cut <= 5; cut++ { t.Run(fmt.Sprintf("%s/after=%t/write=%d", stage, after, cut), func(t *testing.T) { opt := generationRootRunnerDir(t) id := rootFloorIdentity{Deployment: "deploy", Box: "box", Enrollment: "en"} f, err := openRootProtocolFloors(floorDir(opt), id, true) if err != nil { t.Fatal(err) } a := moves.Floor{Min: 1, MinRelease: "v1.0.0"} b := moves.Floor{Min: 2, MinRelease: "v2.0.0"} if err := f.reconstruct(t.Context(), rootFloorReconstruction{Identity: id, Floor: &a, Source: "history", Digest: floorTestDigest, Authorization: floorTestAuthorization(t, f)}); err != nil { t.Fatal(err) } n := 0 injected := errors.New("interrupted syscall") fault := func(_ string, point string, op func() error) error { if point != stage { return op() } n++ if n != cut { return op() } if after { if err := op(); err != nil { return err } } return injected } broken := f broken.private = rootstate.WithWriteFault(f.private, fault) broken.public = rootstate.WithWriteFault(f.public, fault) if err := broken.refresh(t.Context(), &b, nil, nil); !errors.Is(err, injected) { t.Fatalf("cut not reached: %v", err) } f.close() f, err = openRootProtocolFloors(floorDir(opt), id, false) if err != nil { t.Fatal(err) } defer f.close() // Before the second write's rename B is not visible. Thereafter the // selector itself retains B even before accepted.json is replaced. bound, err := f.bound() if err != nil { t.Fatal(err) } bVisible := cut > 2 || cut == 2 && (stage == "parent-sync" || stage == "rename" && after) if bVisible && (bound == nil || *bound != b) { t.Fatalf("lost visible deny: %+v", bound) } if p, e := readRootProtocolProjection(floorDir(opt)); e == nil && p.Floor != nil && *p.Floor == b { t.Fatal("failed syscall acknowledged B") } if err := f.refresh(t.Context(), &b, nil, nil); err != nil { t.Fatal("retry", err) } p, err := readRootProtocolProjection(floorDir(opt)) if err != nil || p.Floor == nil || *p.Floor != b { t.Fatal("retry did not durably accept B", p, err) } if err := f.refresh(t.Context(), &a, nil, nil); err == nil { t.Fatal("regressed after recovery") } bound, err = f.bound() if err != nil || bound == nil || *bound != b { t.Fatal("weakened deny", bound, err) } }) } } } }