goscript 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (264) hide show
  1. package/cmd/goscript/cmd-compile.go +7 -0
  2. package/cmd/goscript/cmd_compile_test.go +83 -0
  3. package/compiler/compile-request.go +3 -0
  4. package/compiler/compiler-cache.go +828 -0
  5. package/compiler/compiler-cache_test.go +705 -0
  6. package/compiler/config.go +2 -0
  7. package/compiler/index.test.ts +26 -1
  8. package/compiler/index.ts +5 -0
  9. package/compiler/lowered-program.go +31 -20
  10. package/compiler/lowering.go +349 -93
  11. package/compiler/lowering_bench_test.go +1 -0
  12. package/compiler/override-facts.go +309 -8
  13. package/compiler/override-parity-verifier.go +45 -1
  14. package/compiler/override-parity-verifier_test.go +100 -0
  15. package/compiler/override-registry_test.go +1 -0
  16. package/compiler/package-graph.go +40 -12
  17. package/compiler/package-graph_test.go +29 -0
  18. package/compiler/runtime-contract.go +8 -0
  19. package/compiler/service.go +98 -11
  20. package/compiler/skeleton_test.go +110 -14
  21. package/compiler/typescript-emitter.go +120 -23
  22. package/dist/compiler/index.d.ts +2 -0
  23. package/dist/compiler/index.js +3 -0
  24. package/dist/compiler/index.js.map +1 -1
  25. package/dist/gs/builtin/builtin.d.ts +24 -33
  26. package/dist/gs/builtin/builtin.js +54 -61
  27. package/dist/gs/builtin/builtin.js.map +1 -1
  28. package/dist/gs/builtin/hostio.d.ts +1 -0
  29. package/dist/gs/builtin/hostio.js +1 -1
  30. package/dist/gs/builtin/hostio.js.map +1 -1
  31. package/dist/gs/builtin/index.d.ts +1 -0
  32. package/dist/gs/builtin/index.js +1 -0
  33. package/dist/gs/builtin/index.js.map +1 -1
  34. package/dist/gs/builtin/panic.d.ts +18 -0
  35. package/dist/gs/builtin/panic.js +98 -0
  36. package/dist/gs/builtin/panic.js.map +1 -0
  37. package/dist/gs/builtin/slice.d.ts +10 -0
  38. package/dist/gs/builtin/slice.js +110 -53
  39. package/dist/gs/builtin/slice.js.map +1 -1
  40. package/dist/gs/builtin/type.js +15 -3
  41. package/dist/gs/builtin/type.js.map +1 -1
  42. package/dist/gs/builtin/varRef.d.ts +1 -1
  43. package/dist/gs/builtin/varRef.js +3 -2
  44. package/dist/gs/builtin/varRef.js.map +1 -1
  45. package/dist/gs/bytes/bytes.gs.js +51 -38
  46. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  47. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  48. package/dist/gs/bytes/reader.gs.js +6 -7
  49. package/dist/gs/bytes/reader.gs.js.map +1 -1
  50. package/dist/gs/cmp/index.d.ts +1 -1
  51. package/dist/gs/cmp/index.js +43 -10
  52. package/dist/gs/cmp/index.js.map +1 -1
  53. package/dist/gs/context/context.d.ts +2 -2
  54. package/dist/gs/context/context.js +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/embed/index.js +1 -1
  57. package/dist/gs/embed/index.js.map +1 -1
  58. package/dist/gs/encoding/binary/index.js +201 -8
  59. package/dist/gs/encoding/binary/index.js.map +1 -1
  60. package/dist/gs/encoding/json/index.d.ts +5 -0
  61. package/dist/gs/encoding/json/index.js +388 -25
  62. package/dist/gs/encoding/json/index.js.map +1 -1
  63. package/dist/gs/errors/errors.js +17 -24
  64. package/dist/gs/errors/errors.js.map +1 -1
  65. package/dist/gs/fmt/fmt.js +129 -35
  66. package/dist/gs/fmt/fmt.js.map +1 -1
  67. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1 -1
  68. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -1
  69. package/dist/gs/internal/bytealg/index.js +43 -8
  70. package/dist/gs/internal/bytealg/index.js.map +1 -1
  71. package/dist/gs/internal/byteorder/index.d.ts +2 -2
  72. package/dist/gs/internal/byteorder/index.js +2 -2
  73. package/dist/gs/internal/byteorder/index.js.map +1 -1
  74. package/dist/gs/io/fs/format.js +2 -2
  75. package/dist/gs/io/fs/format.js.map +1 -1
  76. package/dist/gs/io/fs/fs.d.ts +1 -1
  77. package/dist/gs/io/fs/fs.js +1 -1
  78. package/dist/gs/io/fs/fs.js.map +1 -1
  79. package/dist/gs/io/io.d.ts +21 -21
  80. package/dist/gs/io/io.js +49 -50
  81. package/dist/gs/io/io.js.map +1 -1
  82. package/dist/gs/math/bits/index.js +26 -8
  83. package/dist/gs/math/bits/index.js.map +1 -1
  84. package/dist/gs/math/copysign.gs.js +10 -17
  85. package/dist/gs/math/copysign.gs.js.map +1 -1
  86. package/dist/gs/math/pow.gs.js +5 -0
  87. package/dist/gs/math/pow.gs.js.map +1 -1
  88. package/dist/gs/math/signbit.gs.js +6 -2
  89. package/dist/gs/math/signbit.gs.js.map +1 -1
  90. package/dist/gs/mime/index.js +1 -0
  91. package/dist/gs/mime/index.js.map +1 -1
  92. package/dist/gs/net/http/index.d.ts +6 -6
  93. package/dist/gs/net/http/index.js +507 -43
  94. package/dist/gs/net/http/index.js.map +1 -1
  95. package/dist/gs/os/stat.gs.d.ts +2 -2
  96. package/dist/gs/os/types.gs.d.ts +1 -1
  97. package/dist/gs/os/types.gs.js +1 -1
  98. package/dist/gs/os/types.gs.js.map +1 -1
  99. package/dist/gs/os/types_js.gs.d.ts +1 -1
  100. package/dist/gs/os/types_js.gs.js +7 -7
  101. package/dist/gs/os/types_js.gs.js.map +1 -1
  102. package/dist/gs/os/types_unix.gs.d.ts +1 -1
  103. package/dist/gs/os/types_unix.gs.js +1 -1
  104. package/dist/gs/os/types_unix.gs.js.map +1 -1
  105. package/dist/gs/os/zero_copy_posix.gs.d.ts +1 -1
  106. package/dist/gs/os/zero_copy_posix.gs.js +1 -1
  107. package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
  108. package/dist/gs/path/filepath/match.js +8 -4
  109. package/dist/gs/path/filepath/match.js.map +1 -1
  110. package/dist/gs/path/filepath/path.js +216 -42
  111. package/dist/gs/path/filepath/path.js.map +1 -1
  112. package/dist/gs/path/match.js +6 -3
  113. package/dist/gs/path/match.js.map +1 -1
  114. package/dist/gs/reflect/type.d.ts +5 -4
  115. package/dist/gs/reflect/type.js +29 -11
  116. package/dist/gs/reflect/type.js.map +1 -1
  117. package/dist/gs/slices/slices.js +11 -11
  118. package/dist/gs/slices/slices.js.map +1 -1
  119. package/dist/gs/strconv/atof.gs.js +156 -43
  120. package/dist/gs/strconv/atof.gs.js.map +1 -1
  121. package/dist/gs/strconv/atoi.gs.d.ts +3 -2
  122. package/dist/gs/strconv/atoi.gs.js +86 -67
  123. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  124. package/dist/gs/strconv/ftoa.gs.js +73 -3
  125. package/dist/gs/strconv/ftoa.gs.js.map +1 -1
  126. package/dist/gs/strconv/itoa.gs.d.ts +4 -4
  127. package/dist/gs/strconv/itoa.gs.js +5 -4
  128. package/dist/gs/strconv/itoa.gs.js.map +1 -1
  129. package/dist/gs/strconv/quote.gs.d.ts +1 -1
  130. package/dist/gs/strconv/quote.gs.js +311 -103
  131. package/dist/gs/strconv/quote.gs.js.map +1 -1
  132. package/dist/gs/strings/reader.d.ts +1 -1
  133. package/dist/gs/strings/reader.js +8 -8
  134. package/dist/gs/strings/reader.js.map +1 -1
  135. package/dist/gs/strings/strings.js +87 -61
  136. package/dist/gs/strings/strings.js.map +1 -1
  137. package/dist/gs/sync/atomic/doc_64.gs.d.ts +14 -14
  138. package/dist/gs/sync/atomic/doc_64.gs.js +10 -10
  139. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  140. package/dist/gs/sync/atomic/type.gs.d.ts +22 -22
  141. package/dist/gs/sync/atomic/type.gs.js +4 -4
  142. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  143. package/dist/gs/sync/sync.js +50 -12
  144. package/dist/gs/sync/sync.js.map +1 -1
  145. package/dist/gs/syscall/fs.d.ts +6 -6
  146. package/dist/gs/syscall/fs.js +1 -1
  147. package/dist/gs/syscall/fs.js.map +1 -1
  148. package/dist/gs/time/time.d.ts +18 -18
  149. package/dist/gs/time/time.js +58 -55
  150. package/dist/gs/time/time.js.map +1 -1
  151. package/dist/gs/unicode/tables.d.ts +11 -0
  152. package/dist/gs/unicode/tables.js +635 -0
  153. package/dist/gs/unicode/tables.js.map +1 -0
  154. package/dist/gs/unicode/unicode.d.ts +58 -38
  155. package/dist/gs/unicode/unicode.js +362 -278
  156. package/dist/gs/unicode/unicode.js.map +1 -1
  157. package/go.sum +13 -0
  158. package/gs/builtin/builtin.ts +83 -93
  159. package/gs/builtin/hostio.ts +1 -1
  160. package/gs/builtin/index.ts +1 -0
  161. package/gs/builtin/panic.test.ts +189 -0
  162. package/gs/builtin/panic.ts +107 -0
  163. package/gs/builtin/runtime-contract.test.ts +5 -5
  164. package/gs/builtin/slice.test.ts +23 -0
  165. package/gs/builtin/slice.ts +133 -95
  166. package/gs/builtin/type.ts +16 -3
  167. package/gs/builtin/varRef.ts +4 -2
  168. package/gs/builtin/wide-int.test.ts +41 -0
  169. package/gs/bytes/bytes.gs.ts +54 -41
  170. package/gs/bytes/bytes.test.ts +18 -1
  171. package/gs/bytes/reader.gs.ts +7 -8
  172. package/gs/cmp/index.test.ts +55 -0
  173. package/gs/cmp/index.ts +45 -9
  174. package/gs/context/context.ts +3 -3
  175. package/gs/embed/index.ts +2 -2
  176. package/gs/encoding/binary/index.test.ts +104 -0
  177. package/gs/encoding/binary/index.ts +259 -11
  178. package/gs/encoding/json/index.test.ts +107 -0
  179. package/gs/encoding/json/index.ts +400 -29
  180. package/gs/errors/errors.test.ts +44 -1
  181. package/gs/errors/errors.ts +15 -31
  182. package/gs/fmt/fmt.test.ts +70 -2
  183. package/gs/fmt/fmt.ts +128 -34
  184. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1 -1
  185. package/gs/internal/bytealg/index.test.ts +26 -1
  186. package/gs/internal/bytealg/index.ts +44 -8
  187. package/gs/internal/byteorder/index.ts +6 -4
  188. package/gs/io/fs/format.ts +2 -2
  189. package/gs/io/fs/fs.ts +2 -2
  190. package/gs/io/fs/stat.test.ts +2 -2
  191. package/gs/io/fs/sub.test.ts +2 -2
  192. package/gs/io/fs/walk.test.ts +2 -2
  193. package/gs/io/io.test.ts +47 -5
  194. package/gs/io/io.ts +73 -73
  195. package/gs/io/limit.test.ts +103 -0
  196. package/gs/math/bits/index.test.ts +128 -0
  197. package/gs/math/bits/index.ts +26 -8
  198. package/gs/math/copysign.gs.test.ts +3 -1
  199. package/gs/math/copysign.gs.ts +10 -22
  200. package/gs/math/pow.gs.test.ts +4 -5
  201. package/gs/math/pow.gs.ts +5 -0
  202. package/gs/math/signbit.gs.test.ts +2 -1
  203. package/gs/math/signbit.gs.ts +6 -3
  204. package/gs/mime/index.ts +1 -0
  205. package/gs/net/http/index.test.ts +683 -2
  206. package/gs/net/http/index.ts +598 -57
  207. package/gs/net/http/meta.json +3 -0
  208. package/gs/os/stat.gs.ts +2 -2
  209. package/gs/os/types.gs.ts +2 -2
  210. package/gs/os/types_js.gs.ts +9 -9
  211. package/gs/os/types_unix.gs.ts +2 -2
  212. package/gs/os/zero_copy_posix.gs.ts +2 -2
  213. package/gs/path/filepath/match.test.ts +16 -0
  214. package/gs/path/filepath/match.ts +8 -4
  215. package/gs/path/filepath/path.test.ts +91 -9
  216. package/gs/path/filepath/path.ts +223 -49
  217. package/gs/path/match.test.ts +32 -0
  218. package/gs/path/match.ts +6 -3
  219. package/gs/reflect/deepequal.test.ts +1 -1
  220. package/gs/reflect/field.test.ts +1 -1
  221. package/gs/reflect/function-types.test.ts +6 -6
  222. package/gs/reflect/sliceat.test.ts +13 -13
  223. package/gs/reflect/structof.test.ts +4 -4
  224. package/gs/reflect/type.ts +34 -14
  225. package/gs/reflect/typefor.test.ts +5 -5
  226. package/gs/runtime/pprof/index.test.ts +20 -0
  227. package/gs/runtime/trace/index.test.ts +3 -0
  228. package/gs/slices/slices.test.ts +31 -0
  229. package/gs/slices/slices.ts +11 -11
  230. package/gs/strconv/append.test.ts +99 -0
  231. package/gs/strconv/atof.gs.ts +156 -42
  232. package/gs/strconv/atof.test.ts +45 -0
  233. package/gs/strconv/atoi.gs.ts +87 -69
  234. package/gs/strconv/atoi.test.ts +49 -0
  235. package/gs/strconv/ftoa.gs.ts +85 -10
  236. package/gs/strconv/ftoa.test.ts +43 -0
  237. package/gs/strconv/itoa.gs.ts +10 -9
  238. package/gs/strconv/quote.gs.ts +335 -108
  239. package/gs/strconv/quote.test.ts +111 -0
  240. package/gs/strings/reader.test.ts +10 -10
  241. package/gs/strings/reader.ts +9 -9
  242. package/gs/strings/strings.test.ts +18 -5
  243. package/gs/strings/strings.ts +81 -68
  244. package/gs/sync/atomic/doc_64.gs.ts +24 -24
  245. package/gs/sync/atomic/doc_64.test.ts +5 -5
  246. package/gs/sync/atomic/type.gs.ts +28 -28
  247. package/gs/sync/sync.test.ts +109 -1
  248. package/gs/sync/sync.ts +46 -12
  249. package/gs/syscall/fs.ts +8 -8
  250. package/gs/syscall/net.test.ts +1 -1
  251. package/gs/time/parse.test.ts +45 -0
  252. package/gs/time/time.test.ts +46 -23
  253. package/gs/time/time.ts +69 -66
  254. package/gs/unicode/gen.go +198 -0
  255. package/gs/unicode/tables.ts +646 -0
  256. package/gs/unicode/unicode.test.ts +69 -0
  257. package/gs/unicode/unicode.ts +396 -312
  258. package/package.json +2 -2
  259. package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +0 -20
  260. package/dist/gs/github.com/aperturerobotics/util/conc/index.js +0 -134
  261. package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +0 -1
  262. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +0 -30
  263. package/gs/github.com/aperturerobotics/util/conc/index.ts +0 -172
  264. package/gs/github.com/aperturerobotics/util/conc/meta.json +0 -9
@@ -0,0 +1,705 @@
1
+ package compiler
2
+
3
+ import (
4
+ "context"
5
+ "os"
6
+ "path/filepath"
7
+ "strings"
8
+ "sync"
9
+ "testing"
10
+ )
11
+
12
+ func TestCompileRequestCarriesCacheRoot(t *testing.T) {
13
+ req := NewCompileRequestOwner().NewRequest(Config{
14
+ Dir: ".",
15
+ OutputPath: "out",
16
+ CacheRoot: "cache-root",
17
+ }, []string{"."})
18
+ if req.CacheRoot != "cache-root" {
19
+ t.Fatalf("CacheRoot = %q, want cache-root", req.CacheRoot)
20
+ }
21
+ }
22
+
23
+ func TestCompilePackagesCacheDisabledCreatesNoCacheRoot(t *testing.T) {
24
+ moduleDir := writePackageGraphFixture(t, map[string]string{
25
+ "go.mod": "module example.test/cachedefault\n\ngo 1.25.3\n",
26
+ "main.go": "package cachedefault\nconst Value = 1\n",
27
+ })
28
+ outputDir := filepath.Join(t.TempDir(), "out")
29
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
30
+ comp, err := NewCompiler(&Config{
31
+ Dir: moduleDir,
32
+ OutputPath: outputDir,
33
+ CacheRoot: "",
34
+ }, nil, nil)
35
+ if err != nil {
36
+ t.Fatal(err)
37
+ }
38
+ if _, err := comp.CompilePackages(context.Background(), "."); err != nil {
39
+ t.Fatal(err)
40
+ }
41
+ if _, err := os.Stat(cacheRoot); !os.IsNotExist(err) {
42
+ t.Fatalf("disabled cache root stat err = %v, want not exist", err)
43
+ }
44
+ }
45
+
46
+ func TestCompilePackagesCacheReplaysOutput(t *testing.T) {
47
+ moduleDir := writePackageGraphFixture(t, map[string]string{
48
+ "go.mod": "module example.test/cachereplay\n\ngo 1.25.3\n",
49
+ "main.go": "package cachereplay\nconst Value = 1\n",
50
+ })
51
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
52
+ firstOut := filepath.Join(t.TempDir(), "first")
53
+ result := compileCacheFixture(t, moduleDir, firstOut, cacheRoot)
54
+ if len(result.CompiledPackages) != 1 || result.CompiledPackages[0] != "example.test/cachereplay" {
55
+ t.Fatalf("compiled packages = %#v", result.CompiledPackages)
56
+ }
57
+ manifest := firstCacheManifestPath(t, cacheRoot)
58
+ if manifest == "" {
59
+ t.Fatal("cache manifest not written")
60
+ }
61
+ firstManifestCount := countCacheManifests(t, cacheRoot)
62
+
63
+ secondOut := filepath.Join(t.TempDir(), "second")
64
+ second := compileCacheFixture(t, moduleDir, secondOut, cacheRoot)
65
+ if len(second.CompiledPackages) != 1 || second.CompiledPackages[0] != "example.test/cachereplay" {
66
+ t.Fatalf("cached compiled packages = %#v", second.CompiledPackages)
67
+ }
68
+
69
+ first := readOutputFile(t, firstOut, "example.test/cachereplay", "main.gs.ts")
70
+ replayed := readOutputFile(t, secondOut, "example.test/cachereplay", "main.gs.ts")
71
+ if first != replayed {
72
+ t.Fatalf("cache replay output changed:\nfirst:\n%s\nsecond:\n%s", first, replayed)
73
+ }
74
+ if got := countCacheManifests(t, cacheRoot); got != firstManifestCount {
75
+ t.Fatalf("cache manifests after replay = %d, want %d", got, firstManifestCount)
76
+ }
77
+ }
78
+
79
+ func TestCompilePackagesCacheReplaysEquivalentMultiPackageOutput(t *testing.T) {
80
+ moduleDir := writePackageGraphFixture(t, map[string]string{
81
+ "go.mod": "module example.test/cacheequiv\n\ngo 1.25.3\n",
82
+ "version.txt": "1.2.3\n",
83
+ "main.go": strings.Join([]string{
84
+ "package cacheequiv",
85
+ "import (",
86
+ " _ \"embed\"",
87
+ " \"example.test/cacheequiv/dep\"",
88
+ " _ \"example.test/cacheequiv/side\"",
89
+ ")",
90
+ "//go:embed version.txt",
91
+ "var Version string",
92
+ "const Value = dep.Value",
93
+ "",
94
+ }, "\n"),
95
+ "foo.pb.go": "package cacheequiv\ntype Foo struct{}\n",
96
+ "foo.pb.ts": "export const Foo = \"first\";\n",
97
+ "dep/dep.go": strings.Join([]string{
98
+ "package dep",
99
+ "const Value = 3",
100
+ "",
101
+ }, "\n"),
102
+ "side/side.go": strings.Join([]string{
103
+ "package side",
104
+ "func init() {}",
105
+ "",
106
+ }, "\n"),
107
+ })
108
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
109
+ outputRoot := filepath.Join(t.TempDir(), "out")
110
+ compileCacheFixtureConfig(t, Config{
111
+ AllDependencies: true,
112
+ ProtobufTypeScriptBinding: true,
113
+ }, moduleDir, outputRoot, cacheRoot)
114
+ firstSnapshot := outputTreeSnapshot(t, outputRoot)
115
+ firstManifestCount := countCacheManifests(t, cacheRoot)
116
+
117
+ if err := os.RemoveAll(outputRoot); err != nil {
118
+ t.Fatal(err)
119
+ }
120
+ compileCacheFixtureConfig(t, Config{
121
+ AllDependencies: true,
122
+ ProtobufTypeScriptBinding: true,
123
+ }, moduleDir, outputRoot, cacheRoot)
124
+ secondSnapshot := outputTreeSnapshot(t, outputRoot)
125
+ if firstSnapshot != secondSnapshot {
126
+ t.Fatalf("cache replay output differs:\nfirst:\n%s\nsecond:\n%s", firstSnapshot, secondSnapshot)
127
+ }
128
+ if got := countCacheManifests(t, cacheRoot); got != firstManifestCount {
129
+ t.Fatalf("cache manifests after multi-package replay = %d, want %d", got, firstManifestCount)
130
+ }
131
+ }
132
+
133
+ func TestCompilePackagesCacheInvalidatesSourceChange(t *testing.T) {
134
+ moduleDir := writePackageGraphFixture(t, map[string]string{
135
+ "go.mod": "module example.test/cacheinvalidate\n\ngo 1.25.3\n",
136
+ "main.go": "package cacheinvalidate\nconst Value = 1\n",
137
+ })
138
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
139
+ firstOut := filepath.Join(t.TempDir(), "first")
140
+ compileCacheFixture(t, moduleDir, firstOut, cacheRoot)
141
+
142
+ if err := os.WriteFile(filepath.Join(moduleDir, "main.go"), []byte("package cacheinvalidate\nconst Value = 2\n"), 0o644); err != nil {
143
+ t.Fatal(err)
144
+ }
145
+ secondOut := filepath.Join(t.TempDir(), "second")
146
+ compileCacheFixture(t, moduleDir, secondOut, cacheRoot)
147
+ text := readOutputFile(t, secondOut, "example.test/cacheinvalidate", "main.gs.ts")
148
+ if !strings.Contains(text, "Value: number = 2") {
149
+ t.Fatalf("source change was not reflected in output:\n%s", text)
150
+ }
151
+ if countCacheManifests(t, cacheRoot) < 2 {
152
+ t.Fatal("source change did not create a distinct cache entry")
153
+ }
154
+ }
155
+
156
+ func TestCompilePackagesCacheRequestedModeValidatesImportsBeforeReplay(t *testing.T) {
157
+ moduleDir := writePackageGraphFixture(t, map[string]string{
158
+ "go.mod": "module example.test/cacherequestedimports\n\ngo 1.25.3\n",
159
+ "main.go": strings.Join([]string{
160
+ "package cacherequestedimports",
161
+ "import \"example.test/cacherequestedimports/dep\"",
162
+ "const Value = dep.Value",
163
+ "",
164
+ }, "\n"),
165
+ "dep/dep.go": strings.Join([]string{
166
+ "package dep",
167
+ "const Value = 1",
168
+ "",
169
+ }, "\n"),
170
+ })
171
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
172
+ compileCacheFixture(t, moduleDir, filepath.Join(t.TempDir(), "first"), cacheRoot)
173
+ firstManifestCount := countCacheManifests(t, cacheRoot)
174
+
175
+ if err := os.WriteFile(filepath.Join(moduleDir, "dep", "dep.go"), []byte("package dep\nconst Other = 1\n"), 0o644); err != nil {
176
+ t.Fatal(err)
177
+ }
178
+ comp, err := NewCompiler(&Config{
179
+ Dir: moduleDir,
180
+ OutputPath: filepath.Join(t.TempDir(), "second"),
181
+ CacheRoot: cacheRoot,
182
+ }, nil, nil)
183
+ if err != nil {
184
+ t.Fatal(err)
185
+ }
186
+ if _, err := comp.CompilePackages(context.Background(), "."); err == nil {
187
+ t.Fatal("requested-mode cache replay skipped imported package type validation")
188
+ }
189
+ if got := countCacheManifests(t, cacheRoot); got != firstManifestCount {
190
+ t.Fatalf("invalid imported package wrote cache manifests: got %d want %d", got, firstManifestCount)
191
+ }
192
+ }
193
+
194
+ func TestCompilePackagesCacheInvalidatesEmbedFileChange(t *testing.T) {
195
+ moduleDir := writePackageGraphFixture(t, map[string]string{
196
+ "go.mod": "module example.test/cacheembed\n\ngo 1.25.3\n",
197
+ "version.txt": "1.2.3\n",
198
+ "main.go": strings.Join([]string{
199
+ "package cacheembed",
200
+ "import _ \"embed\"",
201
+ "//go:embed version.txt",
202
+ "var Version string",
203
+ "",
204
+ }, "\n"),
205
+ })
206
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
207
+ firstOut := filepath.Join(t.TempDir(), "first")
208
+ compileCacheFixtureConfig(t, Config{AllDependencies: true}, moduleDir, firstOut, cacheRoot)
209
+
210
+ if err := os.WriteFile(filepath.Join(moduleDir, "version.txt"), []byte("2.0.0\n"), 0o644); err != nil {
211
+ t.Fatal(err)
212
+ }
213
+ secondOut := filepath.Join(t.TempDir(), "second")
214
+ compileCacheFixtureConfig(t, Config{AllDependencies: true}, moduleDir, secondOut, cacheRoot)
215
+ text := readOutputFile(t, secondOut, "example.test/cacheembed", "main.gs.ts")
216
+ if !strings.Contains(text, `Version: string = "2.0.0\n"`) {
217
+ t.Fatalf("embed file change was not reflected in output:\n%s", text)
218
+ }
219
+ if countCacheManifests(t, cacheRoot) < 2 {
220
+ t.Fatal("embed file change did not create a distinct cache entry")
221
+ }
222
+ }
223
+
224
+ func TestCompilePackagesCacheInvalidatesProtobufBindingFileChange(t *testing.T) {
225
+ moduleDir := writePackageGraphFixture(t, map[string]string{
226
+ "go.mod": "module example.test/cachepb\n\ngo 1.25.3\n",
227
+ "foo.pb.go": `package cachepb
228
+
229
+ type Foo struct{}
230
+ `,
231
+ "foo.pb.ts": `export const Foo = "first";
232
+ `,
233
+ })
234
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
235
+ outputRoot := filepath.Join(t.TempDir(), "out")
236
+ compileCacheFixtureConfig(t, Config{ProtobufTypeScriptBinding: true}, moduleDir, outputRoot, cacheRoot)
237
+
238
+ if err := os.WriteFile(filepath.Join(moduleDir, "foo.pb.ts"), []byte(`export const Bar = "second";
239
+ `), 0o644); err != nil {
240
+ t.Fatal(err)
241
+ }
242
+ if err := os.RemoveAll(outputRoot); err != nil {
243
+ t.Fatal(err)
244
+ }
245
+ compileCacheFixtureConfig(t, Config{ProtobufTypeScriptBinding: true}, moduleDir, outputRoot, cacheRoot)
246
+ text := readOutputFile(t, outputRoot, "example.test/cachepb", "foo.pb.ts")
247
+ if strings.Contains(text, "__protobufTypeScriptMessage = __protobuf_ts.Foo") {
248
+ t.Fatalf("protobuf binding change was not reflected in output:\n%s", text)
249
+ }
250
+ if countCacheManifests(t, cacheRoot) < 2 {
251
+ t.Fatal("protobuf binding change did not create a distinct cache entry")
252
+ }
253
+ }
254
+
255
+ func TestCompilerCacheKeyInvalidatesRequestAndModuleInputs(t *testing.T) {
256
+ moduleDir := writePackageGraphFixture(t, map[string]string{
257
+ "go.mod": "module example.test/cachekey\n\ngo 1.25.3\n",
258
+ "main.go": "package cachekey\nconst Value = 1\n",
259
+ })
260
+ baseReq := &CompileRequest{
261
+ Patterns: []string{"."},
262
+ Dir: moduleDir,
263
+ OutputPath: filepath.Join(t.TempDir(), "out"),
264
+ CacheRoot: filepath.Join(t.TempDir(), "cache"),
265
+ DependencyMode: DependencyModeRequested,
266
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
267
+ }
268
+ graph := singleNodeCacheGraph(moduleDir, "example.test/cachekey")
269
+ baseKey := generatedCacheKey(t, baseReq, graph)
270
+
271
+ cases := []struct {
272
+ name string
273
+ edit func(*CompileRequest)
274
+ }{
275
+ {name: "build flags", edit: func(req *CompileRequest) { req.BuildFlags = []string{"tags=cachetest"} }},
276
+ {name: "package blocklist", edit: func(req *CompileRequest) { req.PackageBlocklist = []string{"example.test/blocked"} }},
277
+ {name: "dependency mode", edit: func(req *CompileRequest) { req.DependencyMode = DependencyModeAll }},
278
+ {name: "runtime mode", edit: func(req *CompileRequest) { req.RuntimeEmissionMode = RuntimeEmissionModeReference }},
279
+ {name: "test variants", edit: func(req *CompileRequest) { req.Tests = true }},
280
+ }
281
+ for _, tc := range cases {
282
+ t.Run(tc.name, func(t *testing.T) {
283
+ req := *baseReq
284
+ tc.edit(&req)
285
+ if key := generatedCacheKey(t, &req, graph); key == baseKey {
286
+ t.Fatalf("%s did not change generated package key", tc.name)
287
+ }
288
+ })
289
+ }
290
+
291
+ if err := os.WriteFile(filepath.Join(moduleDir, "go.mod"), []byte("module example.test/cachekey\n\ngo 1.25.3\n\nrequire example.test/dep v0.0.0\n"), 0o644); err != nil {
292
+ t.Fatal(err)
293
+ }
294
+ if key := generatedCacheKey(t, baseReq, graph); key == baseKey {
295
+ t.Fatal("module file change did not change generated package key")
296
+ }
297
+ }
298
+
299
+ func TestCompilerCacheKeyTracksOverridesAndProtobufOutputRelation(t *testing.T) {
300
+ moduleDir := writePackageGraphFixture(t, map[string]string{
301
+ "go.mod": "module example.test/cachekeypb\n\ngo 1.25.3\n",
302
+ "foo.pb.go": "package cachekeypb\ntype Foo struct{}\n",
303
+ "foo.pb.ts": "export const Foo = \"first\";\n",
304
+ })
305
+ req := &CompileRequest{
306
+ Patterns: []string{"."},
307
+ Dir: moduleDir,
308
+ OutputPath: filepath.Join(t.TempDir(), "out-a"),
309
+ CacheRoot: filepath.Join(t.TempDir(), "cache"),
310
+ DependencyMode: DependencyModeRequested,
311
+ RuntimeEmissionMode: RuntimeEmissionModeEmit,
312
+ ProtobufTypeScriptBinding: true,
313
+ }
314
+ graph := singleNodeCacheGraph(moduleDir, "example.test/cachekeypb")
315
+ graph.Nodes[0].CompiledGoFiles = []string{filepath.Join(moduleDir, "foo.pb.go")}
316
+ graph.Nodes[0].GoFiles = graph.Nodes[0].CompiledGoFiles
317
+ baseKey := generatedCacheKey(t, req, graph)
318
+
319
+ outputReq := *req
320
+ outputReq.OutputPath = filepath.Join(t.TempDir(), "out-b")
321
+ if key := generatedCacheKey(t, &outputReq, graph); key == baseKey {
322
+ t.Fatal("protobuf output root relation did not change generated package key")
323
+ }
324
+
325
+ plan := &overrideCopyPlan{packages: []overrideCopyPackage{{
326
+ path: "example.test/cachekeypb",
327
+ files: []overrideCopyFile{{path: "example.test/cachekeypb/index.ts", data: []byte("first")}},
328
+ }}}
329
+ changedPlan := &overrideCopyPlan{packages: []overrideCopyPackage{{
330
+ path: "example.test/cachekeypb",
331
+ files: []overrideCopyFile{{path: "example.test/cachekeypb/index.ts", data: []byte("second")}},
332
+ }}}
333
+ baseOverrideKey := copiedCacheKey(t, req, graph, plan)
334
+ if key := copiedCacheKey(t, req, graph, changedPlan); key == baseOverrideKey {
335
+ t.Fatal("override file content did not change copied package key")
336
+ }
337
+ }
338
+
339
+ func TestCompilerCacheFastReplayRequiresTypedGraphForStrictParity(t *testing.T) {
340
+ graph := &PackageGraph{
341
+ Nodes: []*PackageGraphNode{{
342
+ PkgPath: "example.test/override",
343
+ OverrideCandidate: true,
344
+ }},
345
+ }
346
+ if overrideParityRequiresTypedGraph(graph, &OverrideFacts{packages: map[string]overridePackageFacts{
347
+ "example.test/override": {
348
+ parity: overrideParityLedger{
349
+ Strict: true,
350
+ Symbols: map[string]overrideParityEntry{"Value": {Status: overrideParityStatusReal}},
351
+ },
352
+ },
353
+ }}) {
354
+ return
355
+ }
356
+ t.Fatal("strict override parity did not require a typed graph")
357
+ }
358
+
359
+ func TestCompilerCacheFastReplayAllowsUntypedOverrideMetadata(t *testing.T) {
360
+ graph := &PackageGraph{
361
+ Nodes: []*PackageGraphNode{{
362
+ PkgPath: "context",
363
+ OverrideCandidate: true,
364
+ }},
365
+ }
366
+ if overrideParityRequiresTypedGraph(graph, &OverrideFacts{packages: map[string]overridePackageFacts{
367
+ "context": {metadata: newOverrideMetadata()},
368
+ }}) {
369
+ t.Fatal("metadata-only override required a typed graph")
370
+ }
371
+ }
372
+
373
+ func TestCompilePackagesCacheCorruptionFallsBackToFreshCompile(t *testing.T) {
374
+ moduleDir := writePackageGraphFixture(t, map[string]string{
375
+ "go.mod": "module example.test/cachecorrupt\n\ngo 1.25.3\n",
376
+ "main.go": "package cachecorrupt\nconst Value = 1\n",
377
+ })
378
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
379
+ firstOut := filepath.Join(t.TempDir(), "first")
380
+ compileCacheFixture(t, moduleDir, firstOut, cacheRoot)
381
+
382
+ blob := firstCacheBlobPath(t, cacheRoot)
383
+ if blob == "" {
384
+ t.Fatal("cache blob not written")
385
+ }
386
+ if err := os.WriteFile(blob, []byte("corrupt"), 0o644); err != nil {
387
+ t.Fatal(err)
388
+ }
389
+ secondOut := filepath.Join(t.TempDir(), "second")
390
+ compileCacheFixture(t, moduleDir, secondOut, cacheRoot)
391
+ text := readOutputFile(t, secondOut, "example.test/cachecorrupt", "main.gs.ts")
392
+ if !strings.Contains(text, "Value: number = 1") {
393
+ t.Fatalf("fresh fallback output missing value:\n%s", text)
394
+ }
395
+ }
396
+
397
+ func TestCompilePackagesCacheInvalidManifestEntriesFallBackToFreshCompile(t *testing.T) {
398
+ cases := []struct {
399
+ name string
400
+ tamper func(*testing.T, string)
401
+ }{
402
+ {
403
+ name: "missing blob",
404
+ tamper: func(t *testing.T, cacheRoot string) {
405
+ t.Helper()
406
+ if err := os.Remove(firstCacheBlobPath(t, cacheRoot)); err != nil {
407
+ t.Fatal(err)
408
+ }
409
+ },
410
+ },
411
+ {
412
+ name: "bad key",
413
+ tamper: func(t *testing.T, cacheRoot string) {
414
+ t.Helper()
415
+ manifest := firstCacheManifestPath(t, cacheRoot)
416
+ data, err := os.ReadFile(manifest)
417
+ if err != nil {
418
+ t.Fatal(err)
419
+ }
420
+ data = []byte(strings.Replace(string(data), `"key": "`, `"key": "bad`, 1))
421
+ if err := os.WriteFile(manifest, data, 0o644); err != nil {
422
+ t.Fatal(err)
423
+ }
424
+ },
425
+ },
426
+ {
427
+ name: "unsupported schema",
428
+ tamper: func(t *testing.T, cacheRoot string) {
429
+ t.Helper()
430
+ manifest := firstCacheManifestPath(t, cacheRoot)
431
+ data, err := os.ReadFile(manifest)
432
+ if err != nil {
433
+ t.Fatal(err)
434
+ }
435
+ data = []byte(strings.Replace(string(data), compilerCacheSchema, "unsupported-schema", 1))
436
+ if err := os.WriteFile(manifest, data, 0o644); err != nil {
437
+ t.Fatal(err)
438
+ }
439
+ },
440
+ },
441
+ {
442
+ name: "incomplete entry",
443
+ tamper: func(t *testing.T, cacheRoot string) {
444
+ t.Helper()
445
+ if err := os.Remove(filepath.Join(filepath.Dir(firstCacheManifestPath(t, cacheRoot)), "complete")); err != nil {
446
+ t.Fatal(err)
447
+ }
448
+ },
449
+ },
450
+ }
451
+
452
+ for _, tc := range cases {
453
+ t.Run(tc.name, func(t *testing.T) {
454
+ moduleDir := writePackageGraphFixture(t, map[string]string{
455
+ "go.mod": "module example.test/cacheinvalidmanifest\n\ngo 1.25.3\n",
456
+ "main.go": "package cacheinvalidmanifest\nconst Value = 1\n",
457
+ })
458
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
459
+ compileCacheFixture(t, moduleDir, filepath.Join(t.TempDir(), "first"), cacheRoot)
460
+ tc.tamper(t, cacheRoot)
461
+
462
+ secondOut := filepath.Join(t.TempDir(), "second")
463
+ compileCacheFixture(t, moduleDir, secondOut, cacheRoot)
464
+ text := readOutputFile(t, secondOut, "example.test/cacheinvalidmanifest", "main.gs.ts")
465
+ if !strings.Contains(text, "Value: number = 1") {
466
+ t.Fatalf("fresh fallback output missing value:\n%s", text)
467
+ }
468
+ })
469
+ }
470
+ }
471
+
472
+ func TestCompilerCacheGoEmbedFilesTreatsQuestionAsPattern(t *testing.T) {
473
+ moduleDir := writePackageGraphFixture(t, map[string]string{
474
+ "go.mod": "module example.test/cacheembedpattern\n\ngo 1.25.3\n",
475
+ "a.txt": "one\n",
476
+ })
477
+ files, err := compilerCacheGoEmbedFiles(moduleDir, "?.txt")
478
+ if err != nil {
479
+ t.Fatal(err)
480
+ }
481
+ if len(files) != 1 || !strings.HasSuffix(files[0], string(filepath.Separator)+"a.txt") {
482
+ t.Fatalf("embed pattern files = %#v, want a.txt", files)
483
+ }
484
+ }
485
+
486
+ func TestCompilePackagesCacheConcurrentWritersConverge(t *testing.T) {
487
+ moduleDir := writePackageGraphFixture(t, map[string]string{
488
+ "go.mod": "module example.test/cacheconcurrent\n\ngo 1.25.3\n",
489
+ "main.go": "package cacheconcurrent\nconst Value = 1\n",
490
+ })
491
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
492
+ outputRoots := make([]string, 4)
493
+ for idx := range outputRoots {
494
+ outputRoots[idx] = filepath.Join(t.TempDir(), "out")
495
+ }
496
+ var wg sync.WaitGroup
497
+ errs := make(chan error, 4)
498
+ for idx := range 4 {
499
+ wg.Add(1)
500
+ go func(idx int) {
501
+ defer wg.Done()
502
+ comp, err := NewCompiler(&Config{
503
+ Dir: moduleDir,
504
+ OutputPath: outputRoots[idx],
505
+ CacheRoot: cacheRoot,
506
+ }, nil, nil)
507
+ if err != nil {
508
+ errs <- err
509
+ return
510
+ }
511
+ _, err = comp.CompilePackages(context.Background(), ".")
512
+ errs <- err
513
+ }(idx)
514
+ }
515
+ wg.Wait()
516
+ close(errs)
517
+ for err := range errs {
518
+ if err != nil {
519
+ t.Fatal(err)
520
+ }
521
+ }
522
+ if count := countCacheManifests(t, cacheRoot); count == 0 {
523
+ t.Fatal("concurrent writers did not leave any complete cache manifests")
524
+ }
525
+ }
526
+
527
+ func TestCompilePackagesCacheUnsafeManifestPathFallsBackToFreshCompile(t *testing.T) {
528
+ moduleDir := writePackageGraphFixture(t, map[string]string{
529
+ "go.mod": "module example.test/cacheunsafe\n\ngo 1.25.3\n",
530
+ "main.go": "package cacheunsafe\nconst Value = 1\n",
531
+ })
532
+ cacheRoot := filepath.Join(t.TempDir(), "cache")
533
+ compileCacheFixture(t, moduleDir, filepath.Join(t.TempDir(), "first"), cacheRoot)
534
+
535
+ manifest := firstCacheManifestPath(t, cacheRoot)
536
+ data, err := os.ReadFile(manifest)
537
+ if err != nil {
538
+ t.Fatal(err)
539
+ }
540
+ data = []byte(strings.Replace(string(data), "@goscript/example.test/cacheunsafe/main.gs.ts", "../escape.ts", 1))
541
+ if err := os.WriteFile(manifest, data, 0o644); err != nil {
542
+ t.Fatal(err)
543
+ }
544
+
545
+ secondOut := filepath.Join(t.TempDir(), "second")
546
+ compileCacheFixture(t, moduleDir, secondOut, cacheRoot)
547
+ if _, err := os.Stat(filepath.Join(secondOut, "..", "escape.ts")); !os.IsNotExist(err) {
548
+ t.Fatalf("unsafe replay path stat err = %v, want not exist", err)
549
+ }
550
+ }
551
+
552
+ func singleNodeCacheGraph(moduleDir, importPath string) *PackageGraph {
553
+ file := filepath.Join(moduleDir, "main.go")
554
+ node := &PackageGraphNode{
555
+ ID: importPath,
556
+ PkgPath: importPath,
557
+ Name: filepath.Base(importPath),
558
+ ModulePath: importPath,
559
+ ModuleDir: moduleDir,
560
+ GoFiles: []string{file},
561
+ CompiledGoFiles: []string{file},
562
+ Requested: true,
563
+ }
564
+ return &PackageGraph{
565
+ RequestedPatterns: []string{"."},
566
+ RequestedPackagePaths: []string{importPath},
567
+ Nodes: []*PackageGraphNode{node},
568
+ NodesByPackagePath: map[string]*PackageGraphNode{importPath: node},
569
+ }
570
+ }
571
+
572
+ func generatedCacheKey(t *testing.T, req *CompileRequest, graph *PackageGraph) string {
573
+ t.Helper()
574
+ entries := NewCompilerCacheOwner().Entries(req, graph, nil)
575
+ if len(entries) == 0 {
576
+ t.Fatal("no cache entries")
577
+ }
578
+ return entries[0].key
579
+ }
580
+
581
+ func copiedCacheKey(t *testing.T, req *CompileRequest, graph *PackageGraph, plan *overrideCopyPlan) string {
582
+ t.Helper()
583
+ entries := NewCompilerCacheOwner().Entries(req, graph, plan)
584
+ for _, entry := range entries {
585
+ if entry.kind == compilerCacheEntryCopied {
586
+ return entry.key
587
+ }
588
+ }
589
+ t.Fatal("no copied cache entry")
590
+ return ""
591
+ }
592
+
593
+ func compileCacheFixture(t *testing.T, moduleDir, outputDir, cacheRoot string) *CompilationResult {
594
+ t.Helper()
595
+ return compileCacheFixtureConfig(t, Config{}, moduleDir, outputDir, cacheRoot)
596
+ }
597
+
598
+ func compileCacheFixtureConfig(t *testing.T, config Config, moduleDir, outputDir, cacheRoot string) *CompilationResult {
599
+ t.Helper()
600
+ config.Dir = moduleDir
601
+ config.OutputPath = outputDir
602
+ config.CacheRoot = cacheRoot
603
+ comp, err := NewCompiler(&config, nil, nil)
604
+ if err != nil {
605
+ t.Fatal(err)
606
+ }
607
+ result, err := comp.CompilePackages(context.Background(), ".")
608
+ if err != nil {
609
+ t.Fatal(err)
610
+ }
611
+ return result
612
+ }
613
+
614
+ func readOutputFile(t *testing.T, outputRoot, importPath, name string) string {
615
+ t.Helper()
616
+ data, err := os.ReadFile(filepath.Join(outputRoot, "@goscript", filepath.FromSlash(importPath), name))
617
+ if err != nil {
618
+ t.Fatal(err)
619
+ }
620
+ return string(data)
621
+ }
622
+
623
+ func outputTreeSnapshot(t *testing.T, root string) string {
624
+ t.Helper()
625
+ var snapshot strings.Builder
626
+ err := filepath.WalkDir(root, func(path string, entry os.DirEntry, err error) error {
627
+ if err != nil {
628
+ return err
629
+ }
630
+ if entry.IsDir() {
631
+ return nil
632
+ }
633
+ rel, err := filepath.Rel(root, path)
634
+ if err != nil {
635
+ return err
636
+ }
637
+ data, err := os.ReadFile(path)
638
+ if err != nil {
639
+ return err
640
+ }
641
+ snapshot.WriteString(filepath.ToSlash(rel))
642
+ snapshot.WriteByte('\n')
643
+ snapshot.Write(data)
644
+ snapshot.WriteByte('\n')
645
+ return nil
646
+ })
647
+ if err != nil {
648
+ t.Fatal(err)
649
+ }
650
+ return snapshot.String()
651
+ }
652
+
653
+ func firstCacheManifestPath(t *testing.T, cacheRoot string) string {
654
+ t.Helper()
655
+ var found string
656
+ err := filepath.WalkDir(cacheRoot, func(path string, entry os.DirEntry, err error) error {
657
+ if err != nil || found != "" {
658
+ return err
659
+ }
660
+ if !entry.IsDir() && entry.Name() == "manifest.json" {
661
+ found = path
662
+ }
663
+ return nil
664
+ })
665
+ if err != nil {
666
+ t.Fatal(err)
667
+ }
668
+ return found
669
+ }
670
+
671
+ func firstCacheBlobPath(t *testing.T, cacheRoot string) string {
672
+ t.Helper()
673
+ var found string
674
+ err := filepath.WalkDir(filepath.Join(cacheRoot, compilerCacheSchema, "blobs"), func(path string, entry os.DirEntry, err error) error {
675
+ if err != nil || found != "" {
676
+ return err
677
+ }
678
+ if !entry.IsDir() {
679
+ found = path
680
+ }
681
+ return nil
682
+ })
683
+ if err != nil {
684
+ t.Fatal(err)
685
+ }
686
+ return found
687
+ }
688
+
689
+ func countCacheManifests(t *testing.T, cacheRoot string) int {
690
+ t.Helper()
691
+ count := 0
692
+ err := filepath.WalkDir(cacheRoot, func(path string, entry os.DirEntry, err error) error {
693
+ if err != nil {
694
+ return err
695
+ }
696
+ if !entry.IsDir() && entry.Name() == "manifest.json" {
697
+ count++
698
+ }
699
+ return nil
700
+ })
701
+ if err != nil {
702
+ t.Fatal(err)
703
+ }
704
+ return count
705
+ }