go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/starlark/docgen/symbols/loader_test.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package symbols 16 17 import ( 18 "fmt" 19 "io" 20 "strings" 21 "testing" 22 23 . "github.com/smartystreets/goconvey/convey" 24 ) 25 26 var srcs = map[string]string{ 27 "init.star": ` 28 load("another.star", _mod="exported") 29 load("third.star", _third="exported") 30 31 broken = pub1 # not defined yet 32 33 pub1 = struct( 34 sym = _mod.func, 35 const = 123, 36 broken1 = unknown, 37 broken2 = _mod.unknown, 38 broken3 = pub1, # pub1 is assumed not defined yet 39 ) 40 pub2 = _mod 41 42 def pub3(): 43 pass 44 45 pub4 = _third.deeper.deep 46 47 pub5 = _mod.deeper.deep( 48 arg1 = 123, 49 arg2 = pub4, 50 ) 51 52 pub6 = pub5 53 `, 54 55 "another.star": ` 56 def _func(): 57 pass 58 59 exported = struct( 60 func = _func, 61 deeper = struct(deep = _func), 62 ) 63 64 `, 65 66 "third.star": ` 67 load("another.star", _mod="exported") 68 exported = _mod 69 `, 70 } 71 72 const expectedInitStar = `init.star = *ast.Namespace at init.star:2:1 { 73 _mod = *ast.Namespace at another.star:5:1 { 74 func = *ast.Function _func at another.star:2:1 75 deeper = *ast.Namespace at another.star:7:5 { 76 deep = *ast.Function _func at another.star:2:1 77 } 78 } 79 _third = *ast.Namespace at another.star:5:1 { 80 func = *ast.Function _func at another.star:2:1 81 deeper = *ast.Namespace at another.star:7:5 { 82 deep = *ast.Function _func at another.star:2:1 83 } 84 } 85 broken = <broken> 86 pub1 = *ast.Namespace at init.star:7:1 { 87 sym = *ast.Function _func at another.star:2:1 88 const = *ast.Var const at init.star:9:5 89 broken1 = <broken> 90 broken2 = <broken> 91 broken3 = <broken> 92 } 93 pub2 = *ast.Namespace at another.star:5:1 { 94 func = *ast.Function _func at another.star:2:1 95 deeper = *ast.Namespace at another.star:7:5 { 96 deep = *ast.Function _func at another.star:2:1 97 } 98 } 99 pub3 = *ast.Function pub3 at init.star:16:1 100 pub4 = *ast.Function _func at another.star:2:1 101 pub5 = *ast.Invocation of *symbols.Term deep at init.star:21:1 { 102 arg1 = *ast.Var arg1 at init.star:22:5 103 arg2 = *ast.Function _func at another.star:2:1 104 } 105 pub6 = *ast.Invocation of *symbols.Term deep at init.star:21:1 { 106 arg1 = *ast.Var arg1 at init.star:22:5 107 arg2 = *ast.Function _func at another.star:2:1 108 } 109 } 110 ` 111 112 const expectedThirdStar = `third.star = *ast.Namespace at third.star:2:1 { 113 _mod = *ast.Namespace at another.star:5:1 { 114 func = *ast.Function _func at another.star:2:1 115 deeper = *ast.Namespace at another.star:7:5 { 116 deep = *ast.Function _func at another.star:2:1 117 } 118 } 119 exported = *ast.Namespace at another.star:5:1 { 120 func = *ast.Function _func at another.star:2:1 121 deeper = *ast.Namespace at another.star:7:5 { 122 deep = *ast.Function _func at another.star:2:1 123 } 124 } 125 } 126 ` 127 128 func TestLoader(t *testing.T) { 129 t.Parallel() 130 131 Convey("Works", t, func() { 132 l := Loader{ 133 Normalize: func(parent, module string) (string, error) { return module, nil }, 134 Source: source(srcs), 135 } 136 137 init, err := l.Load("init.star") 138 So(err, ShouldBeNil) 139 So(symbolToString(init), ShouldEqual, expectedInitStar) 140 141 third, err := l.Load("third.star") 142 So(err, ShouldBeNil) 143 So(symbolToString(third), ShouldEqual, expectedThirdStar) 144 145 // 'another.star' is not reparsed. Both init and third eventually refer to 146 // exact same AST nodes. 147 s1 := Lookup(init, "pub1", "sym") 148 s2 := Lookup(third, "exported", "func") 149 So(s1.Def().Name(), ShouldEqual, "_func") // correct node 150 So(s1.Def(), ShouldEqual, s2.Def()) // exact same pointers 151 }) 152 153 Convey("Recursive deps", t, func() { 154 l := Loader{ 155 Normalize: func(parent, module string) (string, error) { return module, nil }, 156 Source: source(map[string]string{ 157 "a.star": `load("b.star", "_")`, 158 "b.star": `load("a.star", "_")`, 159 })} 160 _, err := l.Load("a.star") 161 So(err.Error(), ShouldEqual, "in a.star: in b.star: in a.star: recursive dependency") 162 }) 163 } 164 165 // source makes a source-provider function. 166 func source(src map[string]string) func(string) (string, error) { 167 return func(module string) (string, error) { 168 body, ok := src[module] 169 if !ok { 170 return "", fmt.Errorf("no such module") 171 } 172 return body, nil 173 } 174 } 175 176 func symbolToString(s Symbol) string { 177 buf := strings.Builder{} 178 dumpTree(s, &buf, "") 179 return buf.String() 180 } 181 182 func dumpTree(s Symbol, w io.Writer, indent string) { 183 switch sym := s.(type) { 184 case *BrokenSymbol: 185 fmt.Fprintf(w, "%s%s = <broken>\n", indent, s.Name()) 186 case *Term: 187 node := sym.Def() 188 pos, _ := node.Span() 189 fmt.Fprintf(w, "%s%s = %T %s at %s\n", indent, s.Name(), node, node.Name(), pos) 190 case *Invocation: 191 node := sym.Def() 192 pos, _ := node.Span() 193 fmt.Fprintf(w, "%s%s = %T of %T %s at %s {\n", 194 indent, s.Name(), node, sym.fn, sym.fn.Name(), pos) 195 for _, s := range sym.args { 196 dumpTree(s, w, indent+" ") 197 } 198 fmt.Fprintf(w, "%s}\n", indent) 199 case *Struct: 200 node := sym.Def() 201 pos, _ := node.Span() 202 fmt.Fprintf(w, "%s%s = %T at %s {\n", indent, s.Name(), node, pos) 203 for _, s := range sym.symbols { 204 dumpTree(s, w, indent+" ") 205 } 206 fmt.Fprintf(w, "%s}\n", indent) 207 } 208 }