1
2
3
4
5 package starlark_test
6
7 import (
8 "fmt"
9 "log"
10 "reflect"
11 "sort"
12 "strings"
13 "sync"
14 "sync/atomic"
15 "testing"
16 "unsafe"
17
18 "go.starlark.net/starlark"
19 )
20
21
22
23 func ExampleExecFile() {
24 const data = `
25 print(greeting + ", world")
26 print(repeat("one"))
27 print(repeat("mur", 2))
28 squares = [x*x for x in range(10)]
29 `
30
31
32
33 repeat := func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
34 var s string
35 var n int = 1
36 if err := starlark.UnpackArgs(b.Name(), args, kwargs, "s", &s, "n?", &n); err != nil {
37 return nil, err
38 }
39 return starlark.String(strings.Repeat(s, n)), nil
40 }
41
42
43 thread := &starlark.Thread{
44 Name: "example",
45 Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) },
46 }
47
48
49 predeclared := starlark.StringDict{
50 "greeting": starlark.String("hello"),
51 "repeat": starlark.NewBuiltin("repeat", repeat),
52 }
53
54
55 globals, err := starlark.ExecFile(thread, "apparent/filename.star", data, predeclared)
56 if err != nil {
57 if evalErr, ok := err.(*starlark.EvalError); ok {
58 log.Fatal(evalErr.Backtrace())
59 }
60 log.Fatal(err)
61 }
62
63
64 fmt.Println("\nGlobals:")
65 for _, name := range globals.Keys() {
66 v := globals[name]
67 fmt.Printf("%s (%s) = %s\n", name, v.Type(), v.String())
68 }
69
70
71
72
73
74
75
76
77 }
78
79
80
81 func ExampleThread_Load_sequential() {
82 fakeFilesystem := map[string]string{
83 "c.star": `load("b.star", "b"); c = b + "!"`,
84 "b.star": `load("a.star", "a"); b = a + ", world"`,
85 "a.star": `a = "Hello"`,
86 }
87
88 type entry struct {
89 globals starlark.StringDict
90 err error
91 }
92
93 cache := make(map[string]*entry)
94
95 var load func(_ *starlark.Thread, module string) (starlark.StringDict, error)
96 load = func(_ *starlark.Thread, module string) (starlark.StringDict, error) {
97 e, ok := cache[module]
98 if e == nil {
99 if ok {
100
101 return nil, fmt.Errorf("cycle in load graph")
102 }
103
104
105 cache[module] = nil
106
107
108 data := fakeFilesystem[module]
109 thread := &starlark.Thread{Name: "exec " + module, Load: load}
110 globals, err := starlark.ExecFile(thread, module, data, nil)
111 e = &entry{globals, err}
112
113
114 cache[module] = e
115 }
116 return e.globals, e.err
117 }
118
119 globals, err := load(nil, "c.star")
120 if err != nil {
121 log.Fatal(err)
122 }
123 fmt.Println(globals["c"])
124
125
126
127 }
128
129
130
131 func ExampleThread_Load_parallel() {
132 cache := &cache{
133 cache: make(map[string]*entry),
134 fakeFilesystem: map[string]string{
135 "c.star": `load("a.star", "a"); c = a * 2`,
136 "b.star": `load("a.star", "a"); b = a * 3`,
137 "a.star": `a = 1; print("loaded a")`,
138 },
139 }
140
141
142
143
144
145
146 ch := make(chan string)
147 for _, name := range []string{"b", "c"} {
148 go func(name string) {
149 globals, err := cache.Load(name + ".star")
150 if err != nil {
151 log.Fatal(err)
152 }
153 ch <- fmt.Sprintf("%s = %s", name, globals[name])
154 }(name)
155 }
156 got := []string{<-ch, <-ch}
157 sort.Strings(got)
158 fmt.Println(strings.Join(got, "\n"))
159
160
161
162
163
164 }
165
166
167
168 func TestThreadLoad_ParallelCycle(t *testing.T) {
169 cache := &cache{
170 cache: make(map[string]*entry),
171 fakeFilesystem: map[string]string{
172 "c.star": `load("b.star", "b"); c = b * 2`,
173 "b.star": `load("a.star", "a"); b = a * 3`,
174 "a.star": `load("c.star", "c"); a = c * 5; print("loaded a")`,
175 },
176 }
177
178 ch := make(chan string)
179 for _, name := range "bc" {
180 name := string(name)
181 go func() {
182 _, err := cache.Load(name + ".star")
183 if err == nil {
184 log.Fatalf("Load of %s.star succeeded unexpectedly", name)
185 }
186 ch <- err.Error()
187 }()
188 }
189 got := []string{<-ch, <-ch}
190 sort.Strings(got)
191
192
193
194
195 want1 := []string{
196 "cannot load a.star: cannot load c.star: cycle in load graph",
197 "cannot load b.star: cannot load a.star: cannot load c.star: cycle in load graph",
198 }
199
200
201
202 want2 := []string{
203 "cannot load a.star: cannot load c.star: cannot load b.star: cycle in load graph",
204 "cannot load b.star: cycle in load graph",
205 }
206 if !reflect.DeepEqual(got, want1) && !reflect.DeepEqual(got, want2) {
207 t.Error(got)
208 }
209 }
210
211
212
213
214
215 type cache struct {
216 cacheMu sync.Mutex
217 cache map[string]*entry
218
219 fakeFilesystem map[string]string
220 }
221
222 type entry struct {
223 owner unsafe.Pointer
224 globals starlark.StringDict
225 err error
226 ready chan struct{}
227 }
228
229 func (c *cache) Load(module string) (starlark.StringDict, error) {
230 return c.get(new(cycleChecker), module)
231 }
232
233
234 func (c *cache) get(cc *cycleChecker, module string) (starlark.StringDict, error) {
235 c.cacheMu.Lock()
236 e := c.cache[module]
237 if e != nil {
238 c.cacheMu.Unlock()
239
240
241
242
243 if err := cycleCheck(e, cc); err != nil {
244 return nil, err
245 }
246
247 cc.setWaitsFor(e)
248 <-e.ready
249 cc.setWaitsFor(nil)
250 } else {
251
252 e = &entry{ready: make(chan struct{})}
253 c.cache[module] = e
254 c.cacheMu.Unlock()
255
256 e.setOwner(cc)
257 e.globals, e.err = c.doLoad(cc, module)
258 e.setOwner(nil)
259
260
261 close(e.ready)
262 }
263 return e.globals, e.err
264 }
265
266 func (c *cache) doLoad(cc *cycleChecker, module string) (starlark.StringDict, error) {
267 thread := &starlark.Thread{
268 Name: "exec " + module,
269 Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) },
270 Load: func(_ *starlark.Thread, module string) (starlark.StringDict, error) {
271
272 return c.get(cc, module)
273 },
274 }
275 data := c.fakeFilesystem[module]
276 return starlark.ExecFile(thread, module, data, nil)
277 }
278
279
280
281
282
283
284
285 type cycleChecker struct {
286 waitsFor unsafe.Pointer
287 }
288
289 func (cc *cycleChecker) setWaitsFor(e *entry) {
290 atomic.StorePointer(&cc.waitsFor, unsafe.Pointer(e))
291 }
292
293 func (e *entry) setOwner(cc *cycleChecker) {
294 atomic.StorePointer(&e.owner, unsafe.Pointer(cc))
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 func cycleCheck(e *entry, me *cycleChecker) error {
311 for e != nil {
312 cc := (*cycleChecker)(atomic.LoadPointer(&e.owner))
313 if cc == nil {
314 break
315 }
316 if cc == me {
317 return fmt.Errorf("cycle in load graph")
318 }
319 e = (*entry)(atomic.LoadPointer(&cc.waitsFor))
320 }
321 return nil
322 }
323
View as plain text