ttsc 0.26.2 → 0.27.0

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 (111) hide show
  1. package/README.md +9 -0
  2. package/cmd/ttsc/api_transform.go +17 -3
  3. package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
  4. package/cmd/ttscgraph/disk_realpath_other.go +16 -0
  5. package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
  6. package/cmd/ttscgraph/serve.go +104 -35
  7. package/driver/emit_plugin.go +20 -3
  8. package/driver/graph.go +64 -11
  9. package/driver/input_observation.go +273 -0
  10. package/driver/input_observation_test.go +149 -0
  11. package/driver/plugins.go +384 -4
  12. package/driver/program.go +54 -1
  13. package/driver/resolution_candidates.go +13 -5
  14. package/driver/rewrite.go +45 -2
  15. package/lib/TtscCompiler.d.ts +12 -11
  16. package/lib/TtscCompiler.js +44 -29
  17. package/lib/TtscCompiler.js.map +1 -1
  18. package/lib/compiler/internal/captureProcessOutput.js +2 -2
  19. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  20. package/lib/compiler/internal/compileProjectInMemory.js +4 -3
  21. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  22. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
  23. package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
  24. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  25. package/lib/compiler/internal/runBuild.d.ts +1 -1
  26. package/lib/compiler/internal/runBuild.js +80 -34
  27. package/lib/compiler/internal/runBuild.js.map +1 -1
  28. package/lib/compiler/internal/runSingleFileEmit.js +2 -2
  29. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  30. package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
  31. package/lib/compiler/internal/sharedHostHelpers.js +39 -0
  32. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  33. package/lib/compiler/internal/startResidentTransform.js +12 -4
  34. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  35. package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
  36. package/lib/compiler/internal/transformProjectInMemory.js +239 -35
  37. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  38. package/lib/flags/schema.js +12 -5
  39. package/lib/flags/schema.js.map +1 -1
  40. package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
  41. package/lib/internal/createCanonicalTempDirectory.js +42 -0
  42. package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
  43. package/lib/internal/projectInputPathIdentity.d.ts +3 -0
  44. package/lib/internal/projectInputPathIdentity.js +11 -5
  45. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  46. package/lib/internal/resolveNodeBinary.d.ts +14 -0
  47. package/lib/internal/resolveNodeBinary.js +150 -0
  48. package/lib/internal/resolveNodeBinary.js.map +1 -0
  49. package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
  50. package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
  51. package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
  52. package/lib/launcher/internal/prepareExecution.d.ts +3 -0
  53. package/lib/launcher/internal/prepareExecution.js +42 -14
  54. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  55. package/lib/launcher/internal/runTtsc.js +25 -13
  56. package/lib/launcher/internal/runTtsc.js.map +1 -1
  57. package/lib/launcher/internal/runTtscserver.js +18 -3
  58. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  59. package/lib/launcher/internal/runTtsx.js +2 -0
  60. package/lib/launcher/internal/runTtsx.js.map +1 -1
  61. package/lib/launcher/internal/runtimeHooks.d.ts +37 -1
  62. package/lib/launcher/internal/runtimeHooks.js +653 -62
  63. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  64. package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
  65. package/lib/plugin/internal/buildSourcePlugin.js +754 -72
  66. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  67. package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
  68. package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
  69. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  70. package/lib/register.d.ts +1 -0
  71. package/lib/register.js +46 -0
  72. package/lib/register.js.map +1 -0
  73. package/lib/structures/ITtscCompilerContext.d.ts +6 -3
  74. package/lib/structures/ITtscCompilerTransformation.d.ts +31 -0
  75. package/lib/structures/ITtscPlugin.d.ts +33 -0
  76. package/package.json +16 -9
  77. package/shim/checker/go.mod +9 -0
  78. package/shim/checker/go.sum +22 -0
  79. package/shim/checker/shim.go +75 -0
  80. package/shim/compiler/incremental.go +45 -8
  81. package/src/TtscCompiler.ts +55 -33
  82. package/src/compiler/internal/captureProcessOutput.ts +3 -2
  83. package/src/compiler/internal/compileProjectInMemory.ts +4 -3
  84. package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
  85. package/src/compiler/internal/runBuild.ts +102 -39
  86. package/src/compiler/internal/runSingleFileEmit.ts +2 -2
  87. package/src/compiler/internal/sharedHostHelpers.ts +44 -0
  88. package/src/compiler/internal/startResidentTransform.ts +12 -3
  89. package/src/compiler/internal/transformProjectInMemory.ts +410 -39
  90. package/src/flags/schema.ts +12 -5
  91. package/src/internal/createCanonicalTempDirectory.ts +53 -0
  92. package/src/internal/projectInputPathIdentity.ts +17 -4
  93. package/src/internal/resolveNodeBinary.ts +173 -0
  94. package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
  95. package/src/launcher/internal/prepareExecution.ts +50 -14
  96. package/src/launcher/internal/runTtsc.ts +24 -13
  97. package/src/launcher/internal/runTtscserver.ts +18 -3
  98. package/src/launcher/internal/runTtsx.ts +2 -0
  99. package/src/launcher/internal/runtimeHooks.ts +823 -66
  100. package/src/plugin/internal/buildSourcePlugin.ts +984 -79
  101. package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
  102. package/src/register.ts +47 -0
  103. package/src/structures/ITtscCompilerContext.ts +6 -3
  104. package/src/structures/ITtscCompilerTransformation.ts +39 -0
  105. package/src/structures/ITtscPlugin.ts +36 -0
  106. package/tools/shim_audit/baseline.json +0 -1
  107. package/utility/host.go +9 -3
  108. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
  109. package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
  110. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
  111. package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
@@ -0,0 +1,273 @@
1
+ package driver
2
+
3
+ import (
4
+ "crypto/sha256"
5
+ "encoding/hex"
6
+ "path/filepath"
7
+ "sync"
8
+
9
+ shimtspath "github.com/microsoft/typescript-go/shim/tspath"
10
+ "github.com/microsoft/typescript-go/shim/vfs"
11
+ )
12
+
13
+ var observedDirectoryDigest = func() string {
14
+ digest := sha256.Sum256([]byte("ttsc:host-input:directory\x00"))
15
+ return hex.EncodeToString(digest[:])
16
+ }()
17
+
18
+ type observedInputKind uint8
19
+
20
+ const (
21
+ observedInputMissing observedInputKind = iota
22
+ observedInputFile
23
+ observedInputDirectory
24
+ )
25
+
26
+ type observedInput struct {
27
+ contentHash *string
28
+ kind observedInputKind
29
+ realpath *string
30
+ unstable bool
31
+ }
32
+
33
+ // inputObservationFS records the exact disk state returned through the
34
+ // compiler filesystem. A later transform envelope can therefore prove which
35
+ // bytes and resolution-candidate states produced its resident Program instead
36
+ // of attaching post-compile disk hashes to an earlier result.
37
+ type inputObservationFS struct {
38
+ vfs.FS
39
+ caseSensitive bool
40
+ mu sync.Mutex
41
+ observations map[string]observedInput
42
+ }
43
+
44
+ func newInputObservationFS(inner vfs.FS) *inputObservationFS {
45
+ return &inputObservationFS{
46
+ FS: inner,
47
+ caseSensitive: inner.UseCaseSensitiveFileNames(),
48
+ observations: map[string]observedInput{},
49
+ }
50
+ }
51
+
52
+ func (fs *inputObservationFS) FileExists(path string) bool {
53
+ exists := fs.FS.FileExists(path)
54
+ if exists {
55
+ // Existence participates in resolution, but only ReadFile returns bytes
56
+ // that can influence the resident Program. Do not duplicate every
57
+ // resolver probe with an eager file read.
58
+ fs.observe(path, observedInput{
59
+ kind: observedInputFile,
60
+ realpath: fs.currentRealpath(path),
61
+ })
62
+ } else {
63
+ fs.observe(path, observedInput{kind: observedInputMissing})
64
+ }
65
+ return exists
66
+ }
67
+
68
+ func (fs *inputObservationFS) ReadFile(path string) (string, bool) {
69
+ contents, ok := fs.FS.ReadFile(path)
70
+ if ok {
71
+ digest := sha256.Sum256([]byte(contents))
72
+ hash := hex.EncodeToString(digest[:])
73
+ fs.observe(path, observedInput{
74
+ contentHash: &hash,
75
+ kind: observedInputFile,
76
+ realpath: fs.currentRealpath(path),
77
+ })
78
+ } else {
79
+ fs.observe(path, observedInput{kind: observedInputMissing})
80
+ }
81
+ return contents, ok
82
+ }
83
+
84
+ func (fs *inputObservationFS) DirectoryExists(path string) bool {
85
+ exists := fs.FS.DirectoryExists(path)
86
+ if exists {
87
+ fs.observe(path, observedInput{
88
+ kind: observedInputDirectory,
89
+ realpath: fs.currentRealpath(path),
90
+ })
91
+ } else {
92
+ fs.observe(path, observedInput{kind: observedInputMissing})
93
+ }
94
+ return exists
95
+ }
96
+
97
+ func (fs *inputObservationFS) GetAccessibleEntries(path string) vfs.Entries {
98
+ // DirectoryExists records directory identity on the ordinary compiler path.
99
+ // An empty listing alone cannot distinguish a missing directory from an
100
+ // existing empty one without issuing an extra stat for every enumeration.
101
+ return fs.FS.GetAccessibleEntries(path)
102
+ }
103
+
104
+ func (fs *inputObservationFS) Stat(path string) vfs.FileInfo {
105
+ info := fs.FS.Stat(path)
106
+ if info == nil {
107
+ fs.observe(path, observedInput{kind: observedInputMissing})
108
+ } else if info.IsDir() {
109
+ fs.observe(path, observedInput{
110
+ kind: observedInputDirectory,
111
+ realpath: fs.currentRealpath(path),
112
+ })
113
+ } else {
114
+ fs.observe(path, observedInput{
115
+ kind: observedInputFile,
116
+ realpath: fs.currentRealpath(path),
117
+ })
118
+ }
119
+ return info
120
+ }
121
+
122
+ func (fs *inputObservationFS) Realpath(path string) string {
123
+ realpath := fs.FS.Realpath(path)
124
+ if realpath != "" {
125
+ resolved := filepath.Clean(realpath)
126
+ fs.observeRealpath(path, &resolved)
127
+ }
128
+ return realpath
129
+ }
130
+
131
+ func (fs *inputObservationFS) currentRealpath(path string) *string {
132
+ realpath := fs.FS.Realpath(path)
133
+ if realpath == "" {
134
+ return nil
135
+ }
136
+ resolved := filepath.Clean(realpath)
137
+ return &resolved
138
+ }
139
+
140
+ func (fs *inputObservationFS) observationKey(path string) string {
141
+ if !filepath.IsAbs(path) {
142
+ return ""
143
+ }
144
+ return shimtspath.GetCanonicalFileName(
145
+ shimtspath.NormalizePath(path),
146
+ fs.caseSensitive,
147
+ )
148
+ }
149
+
150
+ func (fs *inputObservationFS) observe(path string, next observedInput) {
151
+ key := fs.observationKey(path)
152
+ if key == "" {
153
+ return
154
+ }
155
+ fs.mu.Lock()
156
+ defer fs.mu.Unlock()
157
+ keys := []string{key}
158
+ // A compiler read can arrive through an 8.3, case-variant, or already-real
159
+ // spelling while resolution recorded the selected lexical alias. Index the
160
+ // returned bytes by the final physical path too, so proof can join the two
161
+ // observations without another disk read.
162
+ if next.contentHash != nil && next.realpath != nil {
163
+ physicalKey := fs.observationKey(*next.realpath)
164
+ if physicalKey != "" && physicalKey != key {
165
+ keys = append(keys, physicalKey)
166
+ }
167
+ }
168
+ for _, observationKey := range keys {
169
+ fs.mergeObservation(observationKey, next)
170
+ }
171
+ }
172
+
173
+ // mergeObservation merges one observation key while the caller holds fs.mu.
174
+ func (fs *inputObservationFS) mergeObservation(key string, next observedInput) {
175
+ previous, found := fs.observations[key]
176
+ if !found {
177
+ fs.observations[key] = next
178
+ return
179
+ }
180
+ if previous.unstable || next.unstable || previous.kind != next.kind || !sameOptionalString(previous.realpath, next.realpath) {
181
+ previous.unstable = true
182
+ previous.contentHash = nil
183
+ previous.realpath = nil
184
+ fs.observations[key] = previous
185
+ return
186
+ }
187
+ if previous.contentHash != nil && next.contentHash != nil && *previous.contentHash != *next.contentHash {
188
+ previous.unstable = true
189
+ previous.contentHash = nil
190
+ previous.realpath = nil
191
+ fs.observations[key] = previous
192
+ return
193
+ }
194
+ if previous.contentHash == nil && next.contentHash != nil {
195
+ previous.contentHash = next.contentHash
196
+ }
197
+ fs.observations[key] = previous
198
+ }
199
+
200
+ func (fs *inputObservationFS) observeRealpath(path string, realpath *string) {
201
+ key := fs.observationKey(path)
202
+ if key == "" {
203
+ return
204
+ }
205
+ fs.mu.Lock()
206
+ defer fs.mu.Unlock()
207
+ previous, found := fs.observations[key]
208
+ if !found {
209
+ // A realpath by itself cannot prove whether the path was a file or
210
+ // directory. The ordinary resolver probes record that state separately.
211
+ return
212
+ }
213
+ if previous.unstable || !sameOptionalString(previous.realpath, realpath) {
214
+ previous.unstable = true
215
+ previous.contentHash = nil
216
+ previous.realpath = nil
217
+ fs.observations[key] = previous
218
+ }
219
+ }
220
+
221
+ func sameOptionalString(left, right *string) bool {
222
+ if left == nil || right == nil {
223
+ return left == nil && right == nil
224
+ }
225
+ return *left == *right
226
+ }
227
+
228
+ // proof returns a stable compiler-time state. A nil hash/realpath with ok=true
229
+ // is an explicit JSON null for a path observed missing; ok=false means the
230
+ // compiler never produced a complete, internally consistent observation.
231
+ func (fs *inputObservationFS) proof(path string) (hash, realpath *string, ok bool) {
232
+ key := fs.observationKey(path)
233
+ if key == "" {
234
+ return nil, nil, false
235
+ }
236
+ fs.mu.Lock()
237
+ observation, found := fs.observations[key]
238
+ // TypeScript-Go can probe a lexical symlink candidate and then read the
239
+ // selected source by its physical filename. Reuse that exact observed read
240
+ // for the alias instead of issuing an eager duplicate read from FileExists.
241
+ if found && !observation.unstable && observation.kind == observedInputFile && observation.contentHash == nil && observation.realpath != nil {
242
+ targetKey := fs.observationKey(*observation.realpath)
243
+ target, targetFound := fs.observations[targetKey]
244
+ if targetFound && !target.unstable && target.kind == observedInputFile && target.contentHash != nil && sameOptionalString(target.realpath, observation.realpath) {
245
+ targetHash := *target.contentHash
246
+ observation.contentHash = &targetHash
247
+ }
248
+ }
249
+ fs.mu.Unlock()
250
+ if !found || observation.unstable {
251
+ return nil, nil, false
252
+ }
253
+ switch observation.kind {
254
+ case observedInputMissing:
255
+ return nil, nil, true
256
+ case observedInputDirectory:
257
+ hash := observedDirectoryDigest
258
+ if observation.realpath == nil {
259
+ return nil, nil, false
260
+ }
261
+ realpath := *observation.realpath
262
+ return &hash, &realpath, true
263
+ case observedInputFile:
264
+ if observation.contentHash == nil || observation.realpath == nil {
265
+ return nil, nil, false
266
+ }
267
+ hash := *observation.contentHash
268
+ realpath := *observation.realpath
269
+ return &hash, &realpath, true
270
+ default:
271
+ return nil, nil, false
272
+ }
273
+ }
@@ -0,0 +1,149 @@
1
+ package driver
2
+
3
+ import (
4
+ "crypto/sha256"
5
+ "encoding/hex"
6
+ "os"
7
+ "os/exec"
8
+ "path/filepath"
9
+ "runtime"
10
+ "testing"
11
+ )
12
+
13
+ func TestInputObservationFSRejectsRestoredContent(t *testing.T) {
14
+ root := t.TempDir()
15
+ file := filepath.Join(root, "external.d.ts")
16
+ if err := os.WriteFile(file, []byte("A"), 0o644); err != nil {
17
+ t.Fatal(err)
18
+ }
19
+ observed := newInputObservationFS(DefaultFS())
20
+ if contents, ok := observed.ReadFile(file); !ok || contents != "A" {
21
+ t.Fatalf("first read = %q, %v", contents, ok)
22
+ }
23
+ if err := os.WriteFile(file, []byte("B"), 0o644); err != nil {
24
+ t.Fatal(err)
25
+ }
26
+ if contents, ok := observed.ReadFile(file); !ok || contents != "B" {
27
+ t.Fatalf("second read = %q, %v", contents, ok)
28
+ }
29
+ if err := os.WriteFile(file, []byte("A"), 0o644); err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ if _, _, ok := observed.proof(file); ok {
33
+ t.Fatal("A-B-A observation unexpectedly produced reusable proof")
34
+ }
35
+ }
36
+
37
+ func TestInputObservationFSProvesReadBytesAndMissingCandidates(t *testing.T) {
38
+ root := t.TempDir()
39
+ file := filepath.Join(root, "selected.ts")
40
+ missing := filepath.Join(root, "superseding.ts")
41
+ contents := "export const selected = true;\n"
42
+ if err := os.WriteFile(file, []byte(contents), 0o644); err != nil {
43
+ t.Fatal(err)
44
+ }
45
+ observed := newInputObservationFS(DefaultFS())
46
+ if !observed.FileExists(file) {
47
+ t.Fatal("selected file was not found")
48
+ }
49
+ if _, ok := observed.ReadFile(file); !ok {
50
+ t.Fatal("selected file was not read")
51
+ }
52
+ if observed.FileExists(missing) {
53
+ t.Fatal("missing candidate unexpectedly exists")
54
+ }
55
+
56
+ digest := sha256.Sum256([]byte(contents))
57
+ wantHash := hex.EncodeToString(digest[:])
58
+ hash, realpath, ok := observed.proof(file)
59
+ if !ok || hash == nil || *hash != wantHash {
60
+ t.Fatalf("selected proof hash = %v, %v", hash, ok)
61
+ }
62
+ wantRealpath, err := filepath.EvalSymlinks(file)
63
+ if err != nil {
64
+ t.Fatal(err)
65
+ }
66
+ if realpath == nil || filepath.Clean(*realpath) != filepath.Clean(wantRealpath) {
67
+ t.Fatalf("selected proof realpath = %v, want %q", realpath, wantRealpath)
68
+ }
69
+ hash, realpath, ok = observed.proof(missing)
70
+ if !ok || hash != nil || realpath != nil {
71
+ t.Fatalf("missing proof = %v, %v, %v", hash, realpath, ok)
72
+ }
73
+ }
74
+
75
+ func TestInputObservationFSHashesCompilerDecodedText(t *testing.T) {
76
+ root := t.TempDir()
77
+ file := filepath.Join(root, "bom.ts")
78
+ text := "export const value = true;\n"
79
+ raw := append([]byte{0xef, 0xbb, 0xbf}, []byte(text)...)
80
+ if err := os.WriteFile(file, raw, 0o644); err != nil {
81
+ t.Fatal(err)
82
+ }
83
+ observed := newInputObservationFS(DefaultFS())
84
+ contents, ok := observed.ReadFile(file)
85
+ if !ok || contents != text {
86
+ t.Fatalf("compiler read = %q, %v; want decoded text", contents, ok)
87
+ }
88
+ digest := sha256.Sum256([]byte(text))
89
+ hash, _, ok := observed.proof(file)
90
+ if !ok || hash == nil || *hash != hex.EncodeToString(digest[:]) {
91
+ t.Fatalf("decoded proof hash = %v, %v", hash, ok)
92
+ }
93
+ }
94
+
95
+ func TestInputObservationFSJoinsSelectedAliasProbeToPhysicalRead(t *testing.T) {
96
+ root := t.TempDir()
97
+ target := filepath.Join(root, "target")
98
+ if err := os.Mkdir(target, 0o755); err != nil {
99
+ t.Fatal(err)
100
+ }
101
+ physical := filepath.Join(target, "value.js")
102
+ contents := "export const value = true;\n"
103
+ if err := os.WriteFile(physical, []byte(contents), 0o644); err != nil {
104
+ t.Fatal(err)
105
+ }
106
+ alias := filepath.Join(root, "alias")
107
+ if runtime.GOOS == "windows" {
108
+ command := exec.Command(
109
+ "node",
110
+ "-e",
111
+ `require("node:fs").symlinkSync(process.argv[1], process.argv[2], "junction")`,
112
+ target,
113
+ alias,
114
+ )
115
+ if output, err := command.CombinedOutput(); err != nil {
116
+ t.Skipf("directory junction unavailable on this host: %v: %s", err, output)
117
+ }
118
+ } else if err := os.Symlink(target, alias); err != nil {
119
+ t.Skipf("directory symlink unavailable on this host: %v", err)
120
+ }
121
+ lexical := filepath.Join(alias, "value.js")
122
+ observed := newInputObservationFS(DefaultFS())
123
+ if !observed.FileExists(lexical) {
124
+ t.Fatal("selected lexical alias was not found")
125
+ }
126
+ if _, ok := observed.ReadFile(physical); !ok {
127
+ t.Fatal("selected physical source was not read")
128
+ }
129
+
130
+ digest := sha256.Sum256([]byte(contents))
131
+ hash, realpath, ok := observed.proof(lexical)
132
+ if !ok || hash == nil || *hash != hex.EncodeToString(digest[:]) {
133
+ t.Fatalf("alias proof hash = %v, %v", hash, ok)
134
+ }
135
+ if realpath == nil {
136
+ t.Fatalf("alias proof realpath = nil, want %q", physical)
137
+ }
138
+ actualInfo, err := os.Stat(*realpath)
139
+ if err != nil {
140
+ t.Fatal(err)
141
+ }
142
+ expectedInfo, err := os.Stat(physical)
143
+ if err != nil {
144
+ t.Fatal(err)
145
+ }
146
+ if !os.SameFile(actualInfo, expectedInfo) {
147
+ t.Fatalf("alias proof realpath = %q, want %q", *realpath, physical)
148
+ }
149
+ }