carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package console
2
3import (
4 "go/ast"
5 "go/parser"
6 "go/token"
7 "io/fs"
8 "path/filepath"
9 "strings"
10 "testing"
11)
12
13func moveRecoveryPolicyBound(f *ast.File) bool {
14 bound := false
15 named := func(e ast.Expr) bool {
16 switch e := e.(type) {
17 case *ast.Ident:
18 return e.Name == "ClaimMoveRecovery"
19 case *ast.SelectorExpr:
20 return e.Sel.Name == "ClaimMoveRecovery"
21 }
22 return false
23 }
24 nonnil := func(e ast.Expr) bool { id, ok := e.(*ast.Ident); return !ok || id.Name != "nil" }
25 ast.Inspect(f, func(n ast.Node) bool {
26 switch n := n.(type) {
27 case *ast.KeyValueExpr:
28 if named(n.Key) && nonnil(n.Value) {
29 bound = true
30 }
31 case *ast.AssignStmt:
32 for i, lhs := range n.Lhs {
33 if named(lhs) && (i >= len(n.Rhs) || nonnil(n.Rhs[i])) {
34 bound = true
35 }
36 }
37 }
38 return true
39 })
40 return bound
41}
42
43func releaseBeforeClose(f *ast.File, fn, release, close string) bool {
44 for _, d := range f.Decls {
45 f, ok := d.(*ast.FuncDecl)
46 if !ok || f.Name.Name != fn {
47 continue
48 }
49 var released, closed token.Pos
50 ast.Inspect(f.Body, func(n ast.Node) bool {
51 call, ok := n.(*ast.CallExpr)
52 if !ok {
53 return true
54 }
55 sel, ok := call.Fun.(*ast.SelectorExpr)
56 if !ok {
57 return true
58 }
59 if sel.Sel.Name == release && released == 0 {
60 released = call.Pos()
61 }
62 if sel.Sel.Name == close && closed == 0 {
63 closed = call.Pos()
64 }
65 return true
66 })
67 return released != 0 && (closed == 0 || released < closed)
68 }
69 return false
70}
71
72func TestClaimMoveRecoveryWiringWaitsForRecordFirstOwners(t *testing.T) {
73 root := "../.."
74 var owners []string
75 for _, owner := range []struct{ file, fn, release, close string }{
76 {"movesparks.go", "closeMove", "ReleaseMove", "MoveClose"},
77 {"movesrecover.go", "finalize", "ReleaseMove", "MoveRolledBackClose"},
78 {"movesrevert.go", "opened", "ReleaseMove", "close"},
79 {"movesabort.go", "abortSource", "releaseAbort", "MoveGuardedAbortComplete"},
80 } {
81 path := filepath.Join(root, "internal/host", owner.file)
82 f, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
83 if err != nil {
84 t.Fatal(err)
85 }
86 if releaseBeforeClose(f, owner.fn, owner.release, owner.close) {
87 owners = append(owners, owner.file+":"+owner.fn)
88 }
89 }
90 if len(owners) == 0 {
91 return
92 }
93 err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
94 if err != nil {
95 return err
96 }
97 if d.IsDir() {
98 if path != root && (strings.HasPrefix(d.Name(), ".") || d.Name() == "testdata" || d.Name() == "vendor") {
99 return filepath.SkipDir
100 }
101 return nil
102 }
103 if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
104 return nil
105 }
106 f, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
107 if err != nil {
108 return err
109 }
110 if moveRecoveryPolicyBound(f) {
111 t.Errorf("%s wires ClaimMoveRecovery while owners release first: %v", path, owners)
112 }
113 return nil
114 })
115 if err != nil {
116 t.Fatal(err)
117 }
118}
119
120func TestClaimMoveRecoveryWiringGuardDetectsBindings(t *testing.T) {
121 for _, src := range []string{
122 `package p; func f(){ c.ClaimMoveRecovery = policy }`,
123 `package p; var c = Config{ClaimMoveRecovery: policy}`,
124 } {
125 f, err := parser.ParseFile(token.NewFileSet(), "fixture.go", src, 0)
126 if err != nil {
127 t.Fatal(err)
128 }
129 if !moveRecoveryPolicyBound(f) {
130 t.Fatal("binding escaped census")
131 }
132 }
133 for _, src := range []string{
134 `package p; func f(){ c.ClaimMoveRecovery = nil }`,
135 `package p; var c = Config{ClaimMoveRecovery: nil}`,
136 } {
137 f, err := parser.ParseFile(token.NewFileSet(), "fixture.go", src, 0)
138 if err != nil {
139 t.Fatal(err)
140 }
141 if moveRecoveryPolicyBound(f) {
142 t.Fatal("nil policy counted as binding")
143 }
144 }
145 for _, tc := range []struct {
146 src string
147 want bool
148 }{
149 {`package p; func owner(){hostclaim.ReleaseMove();provision.MoveClose()}`, true},
150 {`package p; func owner(){provision.MoveClose();hostclaim.ReleaseMove()}`, false},
151 } {
152 f, err := parser.ParseFile(token.NewFileSet(), "fixture.go", tc.src, 0)
153 if err != nil {
154 t.Fatal(err)
155 }
156 if got := releaseBeforeClose(f, "owner", "ReleaseMove", "MoveClose"); got != tc.want {
157 t.Fatalf("release ordering=%v", got)
158 }
159 }
160}
161