package jscriptx import ( "context" "strings" "sync" "testing" ) // recorder 是个带状态的扩展,用来验证作用域之间有没有串。 type recorder struct { mu sync.Mutex tag string seen []string } func (*recorder) Name() string { return "rec" } func (*recorder) Module() (string, string) { return "", "" } func (r *recorder) Bindings() map[string]any { return map[string]any{ "tag": func() string { return r.tag }, "note": func(m string) { r.mu.Lock() defer r.mu.Unlock() r.seen = append(r.seen, m) }, } } func scoped(t *testing.T, r *recorder) context.Context { t.Helper() return WithScope(context.Background(), ScopeExtensions(r)) } // Script.Call 走的是共享池,以前不注入作用域扩展,脚本顶层 import 直接 // ReferenceError。现在带作用域就单造一个 VM。 func TestScopePool_池化调用也能拿到扩展(t *testing.T) { e, _ := New(WithLogger(nil)) defer e.Close() s, err := e.Compile("s.ts", ` rec.note("顶层:" + rec.tag()) export default function () { return rec.tag() }`) if err != nil { t.Fatal(err) } r := &recorder{tag: "A"} got, err := s.Call(scoped(t, r), "") if err != nil { t.Fatalf("带作用域的池化调用该能用扩展: %v", err) } if got != "A" { t.Errorf("拿到 %#v", got) } if len(r.seen) != 1 || r.seen[0] != "顶层:A" { t.Errorf("模块顶层没跑或没拿到扩展: %v", r.seen) } } // 关键:带作用域的 VM 用完必须丢,不能回池——回池就把上一个作用域的扩展 // 漏给下一个调用了。 func TestScopePool_作用域之间不串(t *testing.T) { e, _ := New(WithLogger(nil)) defer e.Close() s, _ := e.Compile("s.ts", `export default function () { return rec.tag() }`) for _, want := range []string{"A", "B", "C"} { r := &recorder{tag: want} got, err := s.Call(scoped(t, r), "") if err != nil { t.Fatal(err) } if got != want { t.Fatalf("拿到的是别的作用域的扩展: 想要 %q,拿到 %#v", want, got) } } } // 不带作用域的调用照旧走池,行为不变。 func TestScopePool_无作用域仍然走池(t *testing.T) { e, _ := New(WithLogger(nil)) defer e.Close() s, _ := e.Compile("s.ts", `export default function () { return 1 }`) ctx := context.Background() for range 5 { if _, err := s.Call(ctx, ""); err != nil { t.Fatal(err) } } // 第一次建一个,后面四次都该命中池 if st := s.Stats(); st.Dropped != 0 { t.Errorf("不带作用域不该丢 VM,Dropped = %d", st.Dropped) } } // 带作用域的 VM 每次都丢,不占池子。 func TestScopePool_带作用域的VM不回池(t *testing.T) { e, _ := New(WithLogger(nil)) defer e.Close() s, _ := e.Compile("s.ts", `export default function () { return rec.tag() }`) before := s.Stats().Dropped for range 3 { if _, err := s.Call(scoped(t, &recorder{tag: "X"}), ""); err != nil { t.Fatal(err) } } if got := s.Stats().Dropped - before; got != 3 { t.Errorf("三次带作用域的调用该丢三个 VM,实际丢了 %d", got) } } // 没有扩展、只有 key 的作用域也不能走池:脚本能读到 scope.key, // 池化 VM 会把上一个作用域的 key 漏给下一个调用。 func TestScopePool_只有key的作用域也不走池(t *testing.T) { e, _ := New(WithLogger(nil)) defer e.Close() s, _ := e.Compile("s.ts", `export default function () { return scope.key }`) for _, want := range []string{"k1", "k2", "k3"} { ctx := WithScope(context.Background(), ScopeKey(want)) got, err := s.Call(ctx, "") if err != nil { t.Fatal(err) } if got != want { t.Fatalf("拿到的是别的作用域的 key: 想要 %q,拿到 %#v", want, got) } } } // 并发下各作用域看到的必须是自己那份。 func TestScopePool_并发不串(t *testing.T) { e, _ := New(WithLogger(nil)) defer e.Close() s, _ := e.Compile("s.ts", `export default function () { return rec.tag() }`) var wg sync.WaitGroup errs := make(chan string, 64) for i := range 32 { wg.Add(1) go func() { defer wg.Done() want := string(rune('a' + i%26)) got, err := s.Call(scoped(t, &recorder{tag: want}), "") if err != nil { errs <- err.Error() return } if got != want { errs <- "串了: 想要 " + want + ",拿到 " + got.(string) } }() } wg.Wait() close(errs) var bad []string for e := range errs { bad = append(bad, e) } if len(bad) > 0 { t.Errorf("%d 次出错:\n%s", len(bad), strings.Join(bad, "\n")) } }