ttsc 0.23.0 → 0.25.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.
- package/LICENSE +21 -21
- package/README.md +66 -63
- package/cmd/ttscgraph/serve.go +264 -48
- package/cmd/ttscgraph/serve_answers_a_project_that_implements_its_own_ambient_declaration_test.go +69 -0
- package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
- package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
- package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
- package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
- package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
- package/cmd/ttscgraph/serve_shards.go +1390 -0
- package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
- package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
- package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
- package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
- package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
- package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
- package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
- package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
- package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
- package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
- package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
- package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
- package/driver/declaration_shape.go +85 -0
- package/driver/program.go +39 -10
- package/go.sum +22 -22
- package/internal/graph/assigned_implementations_of_declaration_file_symbols_leave_no_edge_source_test.go +84 -0
- package/internal/graph/assigned_implementations_of_node_modules_symbols_leave_no_edge_source_test.go +85 -0
- package/internal/graph/build.go +62 -10
- package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
- package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
- package/internal/graph/dump.go +84 -43
- package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
- package/internal/graph/dump_paths.go +134 -11
- package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
- package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
- package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
- package/internal/graph/edges.go +73 -17
- package/internal/graph/exports.go +9 -2
- package/internal/graph/graph.go +47 -7
- package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
- package/internal/graph/provenance.go +11 -1
- package/internal/graph/resolve.go +1 -4
- package/internal/graph/source.go +23 -0
- package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
- package/internal/lspserver/lsp_native_plugin_source.go +5 -6
- package/internal/lspserver/lsp_native_resident.go +17 -24
- package/lib/TtscService.d.ts +1 -6
- package/lib/TtscService.js +2 -2
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/captureProcessOutput.d.ts +26 -0
- package/lib/compiler/internal/captureProcessOutput.js +92 -0
- package/lib/compiler/internal/captureProcessOutput.js.map +1 -0
- package/lib/compiler/internal/compileProjectInMemory.js +4 -7
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/residentCheckProcess.d.ts +0 -2
- package/lib/compiler/internal/residentCheckProcess.js +1 -15
- package/lib/compiler/internal/residentCheckProcess.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +0 -6
- package/lib/compiler/internal/residentTransformProcess.js +0 -25
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/spawnNative.d.ts +14 -6
- package/lib/compiler/internal/spawnNative.js +32 -11
- package/lib/compiler/internal/spawnNative.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.d.ts +1 -5
- package/lib/compiler/internal/startResidentTransform.js +1 -3
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.d.ts +2 -1
- package/lib/launcher/internal/prepareExecution.js +269 -23
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/registerRuntimeHooks.js +6 -1
- package/lib/launcher/internal/registerRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +4 -9
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +2 -55
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +25 -2
- package/lib/launcher/internal/runtimeHooks.js +344 -69
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.js +31 -3
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/descriptorProcessFailure.d.ts +23 -19
- package/lib/plugin/internal/descriptorProcessFailure.js +40 -51
- package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +12 -0
- package/lib/plugin/internal/loadProjectPlugins.js +77 -28
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +8 -8
- package/shim/ast/extra-shim.json +4 -4
- package/shim/ast/go.mod +14 -14
- package/shim/ast/go.sum +20 -20
- package/shim/astnav/go.mod +14 -14
- package/shim/astnav/go.sum +20 -20
- package/shim/bundled/extra-shim.json +1 -1
- package/shim/bundled/go.mod +5 -5
- package/shim/checker/extra-shim.json +31 -31
- package/shim/checker/go.mod +5 -5
- package/shim/compiler/extra-shim.json +4 -4
- package/shim/compiler/go.mod +14 -14
- package/shim/compiler/go.sum +22 -22
- package/shim/core/extra-shim.json +4 -4
- package/shim/core/go.mod +5 -5
- package/shim/diagnosticwriter/go.mod +5 -5
- package/shim/lsp/extra-shim.json +4 -4
- package/shim/lsp/go.mod +16 -16
- package/shim/lsp/go.sum +26 -26
- package/shim/parser/extra-shim.json +4 -4
- package/shim/parser/go.mod +5 -5
- package/shim/printer/go.mod +14 -14
- package/shim/printer/go.sum +22 -22
- package/shim/scanner/extra-shim.json +4 -4
- package/shim/scanner/go.mod +5 -5
- package/shim/transformers/go.mod +14 -14
- package/shim/transformers/go.sum +20 -20
- package/shim/tsoptions/extra-shim.json +4 -4
- package/shim/tsoptions/go.mod +5 -5
- package/shim/tspath/extra-shim.json +4 -4
- package/shim/tspath/go.mod +5 -5
- package/shim/vfs/cachedvfs/extra-shim.json +4 -4
- package/shim/vfs/cachedvfs/go.mod +5 -5
- package/shim/vfs/extra-shim.json +4 -4
- package/shim/vfs/go.mod +5 -5
- package/shim/vfs/osvfs/extra-shim.json +4 -4
- package/shim/vfs/osvfs/go.mod +5 -5
- package/src/TtscService.ts +8 -20
- package/src/compiler/internal/captureProcessOutput.ts +96 -0
- package/src/compiler/internal/compileProjectInMemory.ts +7 -7
- package/src/compiler/internal/residentCheckProcess.ts +1 -21
- package/src/compiler/internal/residentTransformProcess.ts +0 -42
- package/src/compiler/internal/spawnNative.ts +39 -18
- package/src/compiler/internal/startResidentTransform.ts +1 -12
- package/src/launcher/internal/prepareExecution.ts +297 -26
- package/src/launcher/internal/registerRuntimeHooks.ts +11 -2
- package/src/launcher/internal/runTtscserver.ts +4 -9
- package/src/launcher/internal/runTtsx.ts +2 -78
- package/src/launcher/internal/runtimeHooks.ts +385 -82
- package/src/plugin/internal/buildSourcePlugin.ts +36 -3
- package/src/plugin/internal/descriptorProcessFailure.ts +34 -65
- package/src/plugin/internal/loadProjectPlugins.ts +78 -39
- package/tools/gen_shims/go.mod +14 -14
- package/tools/gen_shims/go.sum +12 -12
- package/tools/shim_audit/go.mod +10 -10
- package/tools/shim_audit/go.sum +8 -8
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServeShardsAbandonedGenerationConvergesOnRetry verifies a consumer that
|
|
10
|
+
// abandons a request mid-flight can still converge, and that the facts it
|
|
11
|
+
// eventually commits match the ones it discarded.
|
|
12
|
+
//
|
|
13
|
+
// The producer has no abort path: it commits after the projection succeeds, so
|
|
14
|
+
// a client that walks away leaves the producer's committed generation ahead of
|
|
15
|
+
// the consumer's. Cancellation is therefore discharged by base negotiation
|
|
16
|
+
// rather than by cancellation handling, which is sound only if the abandoned
|
|
17
|
+
// generation is not the sole carrier of its facts. A consumer replaying from
|
|
18
|
+
// its own stale base has to receive those same facts again.
|
|
19
|
+
//
|
|
20
|
+
// 1. Commit one generation, then edit and take a generation the client discards.
|
|
21
|
+
// 2. Edit again and take the generation a recovered client would actually read.
|
|
22
|
+
// 3. Require the store to have advanced once per accepted call and the final
|
|
23
|
+
// shard for the edited source to carry the latest text, not the abandoned one.
|
|
24
|
+
func TestServeShardsAbandonedGenerationConvergesOnRetry(t *testing.T) {
|
|
25
|
+
root := graphSessionFixture(t)
|
|
26
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
27
|
+
if err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
defer session.Close()
|
|
31
|
+
committed, _, _, err := session.SnapshotShards()
|
|
32
|
+
if err != nil {
|
|
33
|
+
t.Fatal(err)
|
|
34
|
+
}
|
|
35
|
+
file := filepath.Join(root, "src", "index.ts")
|
|
36
|
+
indexSource := session.compiler.Program().SourceFile(file)
|
|
37
|
+
if indexSource == nil {
|
|
38
|
+
t.Fatal("fixture source was absent from resident program")
|
|
39
|
+
}
|
|
40
|
+
indexKeyFile := indexSource.FileName()
|
|
41
|
+
|
|
42
|
+
// The generation the client asks for and then abandons before reading.
|
|
43
|
+
if err := os.WriteFile(file, []byte("export class Abandoned {}\n"), 0o644); err != nil {
|
|
44
|
+
t.Fatal(err)
|
|
45
|
+
}
|
|
46
|
+
abandoned, _, changed, err := session.SnapshotShards()
|
|
47
|
+
if err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
if abandoned == nil || !changed || abandoned.BaseGeneration != committed.Generation {
|
|
51
|
+
t.Fatalf("abandoned generation = snapshot:%#v changed:%v", abandoned, changed)
|
|
52
|
+
}
|
|
53
|
+
abandonedKey := session.graphStore.sourceKeys[indexKeyFile]
|
|
54
|
+
|
|
55
|
+
// The producer moved on regardless. A later edit plus the client's retry has
|
|
56
|
+
// to publish a generation that supersedes the abandoned one rather than
|
|
57
|
+
// depending on the consumer having applied it.
|
|
58
|
+
if err := os.WriteFile(file, []byte("export class Converged {}\n"), 0o644); err != nil {
|
|
59
|
+
t.Fatal(err)
|
|
60
|
+
}
|
|
61
|
+
converged, _, changed, err := session.SnapshotShards()
|
|
62
|
+
if err != nil {
|
|
63
|
+
t.Fatal(err)
|
|
64
|
+
}
|
|
65
|
+
if converged == nil || !changed {
|
|
66
|
+
t.Fatalf("converged generation = snapshot:%#v changed:%v", converged, changed)
|
|
67
|
+
}
|
|
68
|
+
if converged.BaseGeneration != abandoned.Generation {
|
|
69
|
+
t.Fatalf(
|
|
70
|
+
"converged base = %q, want the abandoned generation %q the producer actually committed",
|
|
71
|
+
converged.BaseGeneration,
|
|
72
|
+
abandoned.Generation,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
if converged.Sequence != abandoned.Sequence+1 {
|
|
76
|
+
t.Fatalf("converged sequence = %d, want %d", converged.Sequence, abandoned.Sequence+1)
|
|
77
|
+
}
|
|
78
|
+
convergedKey := session.graphStore.sourceKeys[indexKeyFile]
|
|
79
|
+
if convergedKey == "" || convergedKey == abandonedKey {
|
|
80
|
+
t.Fatalf("edited source kept the abandoned shard identity %q", convergedKey)
|
|
81
|
+
}
|
|
82
|
+
if !containsUpsertedShardKey(converged, convergedKey) {
|
|
83
|
+
t.Fatalf("converged generation did not publish the latest shard %q", convergedKey)
|
|
84
|
+
}
|
|
85
|
+
if !containsString(converged.Deletes, abandonedKey) {
|
|
86
|
+
t.Fatalf(
|
|
87
|
+
"converged generation did not supersede the abandoned shard %q: %v",
|
|
88
|
+
abandonedKey,
|
|
89
|
+
converged.Deletes,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServeShardsDeleteRemovedSource verifies a root-set reload names the
|
|
10
|
+
// superseded source shard explicitly while publishing a complete replacement
|
|
11
|
+
// generation. A consumer never has to infer deletion from a missing payload.
|
|
12
|
+
func TestServeShardsDeleteRemovedSource(t *testing.T) {
|
|
13
|
+
root := graphSessionFixture(t)
|
|
14
|
+
keep := filepath.Join(root, "src", "keep.ts")
|
|
15
|
+
writeGraphFile(t, keep, "export const keep = true;\n")
|
|
16
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
17
|
+
if err != nil {
|
|
18
|
+
t.Fatal(err)
|
|
19
|
+
}
|
|
20
|
+
defer session.Close()
|
|
21
|
+
initial, _, _, err := session.SnapshotShards()
|
|
22
|
+
if err != nil {
|
|
23
|
+
t.Fatal(err)
|
|
24
|
+
}
|
|
25
|
+
removed := filepath.Join(root, "src", "index.ts")
|
|
26
|
+
removedSource := session.compiler.Program().SourceFile(removed)
|
|
27
|
+
keepSource := session.compiler.Program().SourceFile(keep)
|
|
28
|
+
if removedSource == nil || keepSource == nil {
|
|
29
|
+
t.Fatal("fixture source was absent from resident program")
|
|
30
|
+
}
|
|
31
|
+
removedKeyFile := removedSource.FileName()
|
|
32
|
+
keepKeyFile := keepSource.FileName()
|
|
33
|
+
removedKey := session.graphStore.sourceKeys[removedKeyFile]
|
|
34
|
+
if removedKey == "" {
|
|
35
|
+
t.Fatal("initial generation omitted removable source shard")
|
|
36
|
+
}
|
|
37
|
+
if err := os.Remove(removed); err != nil {
|
|
38
|
+
t.Fatal(err)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
replacement, mode, changed, err := session.SnapshotShards()
|
|
42
|
+
if err != nil {
|
|
43
|
+
t.Fatal(err)
|
|
44
|
+
}
|
|
45
|
+
if replacement == nil || mode != serveModeReload || !changed || replacement.BaseGeneration != initial.Generation {
|
|
46
|
+
t.Fatalf("replacement generation = snapshot:%#v mode:%q changed:%v", replacement, mode, changed)
|
|
47
|
+
}
|
|
48
|
+
if !containsString(replacement.Deletes, removedKey) {
|
|
49
|
+
t.Fatalf("replacement did not delete removed source shard %q: %v", removedKey, replacement.Deletes)
|
|
50
|
+
}
|
|
51
|
+
if _, exists := session.graphStore.sourceKeys[removedKeyFile]; exists {
|
|
52
|
+
t.Fatal("committed store retained removed source identity")
|
|
53
|
+
}
|
|
54
|
+
if session.graphStore.sourceKeys[keepKeyFile] == "" {
|
|
55
|
+
t.Fatal("replacement generation dropped remaining source")
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
func TestServeShardsKeepReferencedExternalEndpointsOnIncrementalLane(t *testing.T) {
|
|
10
|
+
root := t.TempDir()
|
|
11
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
12
|
+
"compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}`)
|
|
15
|
+
writeGraphFile(t, filepath.Join(root, "src", "api.d.ts"), "export declare function external(): number;\n")
|
|
16
|
+
index := filepath.Join(root, "src", "index.ts")
|
|
17
|
+
writeGraphFile(t, index, "import { external } from './api';\nexport function local(): number { return external() + 1; }\n")
|
|
18
|
+
|
|
19
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
20
|
+
if err != nil {
|
|
21
|
+
t.Fatal(err)
|
|
22
|
+
}
|
|
23
|
+
defer session.Close()
|
|
24
|
+
if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
|
|
25
|
+
t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
|
|
26
|
+
}
|
|
27
|
+
if err := os.WriteFile(index, []byte("import { external } from './api';\nexport function local(): number { return external() + 2; }\n"), 0o644); err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
snapshot, mode, changed, err := session.SnapshotShards()
|
|
31
|
+
if err != nil {
|
|
32
|
+
t.Fatal(err)
|
|
33
|
+
}
|
|
34
|
+
if snapshot == nil || mode != serveModeIncremental || !changed {
|
|
35
|
+
t.Fatalf("external endpoint edit = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
|
|
36
|
+
}
|
|
37
|
+
retained := false
|
|
38
|
+
for _, node := range session.graphStore.nodes {
|
|
39
|
+
if node.External && node.Name == "external" {
|
|
40
|
+
retained = true
|
|
41
|
+
break
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if !retained {
|
|
45
|
+
t.Fatalf(
|
|
46
|
+
"referenced external endpoint left the internal base-node cache: nodes=%#v references=%#v external=%#v",
|
|
47
|
+
session.graphStore.nodes,
|
|
48
|
+
session.graphStore.externalReferences,
|
|
49
|
+
session.graphStore.externalNodes,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
53
|
+
}
|
package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"strings"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServeShardsKeepSourceDistributedDependenciesAtTheBoundary proves the
|
|
10
|
+
// incremental store owns only workspace declarations while provenance still
|
|
11
|
+
// attests to every source the resident checker loaded.
|
|
12
|
+
//
|
|
13
|
+
// 1. Snapshot one workspace source that imports a raw TypeScript package.
|
|
14
|
+
// 2. Assert the dependency owns provenance but no authored graph facts.
|
|
15
|
+
// 3. Edit the dependency and assert the store publishes a complete replacement.
|
|
16
|
+
// 4. Compare the replacement store with the full-dump oracle.
|
|
17
|
+
func TestServeShardsKeepSourceDistributedDependenciesAtTheBoundary(t *testing.T) {
|
|
18
|
+
root := t.TempDir()
|
|
19
|
+
dependencyPath := filepath.Join(root, "node_modules", "dep-src", "src", "index.ts")
|
|
20
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
21
|
+
"compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
|
|
22
|
+
"files": ["src/main.ts"]
|
|
23
|
+
}`)
|
|
24
|
+
writeGraphFile(t, filepath.Join(root, "node_modules", "dep-src", "package.json"), `{
|
|
25
|
+
"name": "dep-src",
|
|
26
|
+
"version": "1.0.0",
|
|
27
|
+
"main": "src/index.ts"
|
|
28
|
+
}`)
|
|
29
|
+
writeGraphFile(t, dependencyPath, "export function dependencyValue(): number { return 1; }\nexport function dependencyInternal(): number { return dependencyValue(); }\n")
|
|
30
|
+
writeGraphFile(t, filepath.Join(root, "src", "main.ts"), "import { dependencyValue } from 'dep-src';\nexport function workspaceValue(): number { return dependencyValue() + 1; }\n")
|
|
31
|
+
|
|
32
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
33
|
+
if err != nil {
|
|
34
|
+
t.Fatal(err)
|
|
35
|
+
}
|
|
36
|
+
defer session.Close()
|
|
37
|
+
if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
|
|
38
|
+
t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
dependencyFile := ""
|
|
42
|
+
dependencyDigest := ""
|
|
43
|
+
for _, source := range session.graphStore.provenance.Sources {
|
|
44
|
+
if strings.Contains(filepath.ToSlash(source.File), "/node_modules/dep-src/") {
|
|
45
|
+
dependencyFile = source.File
|
|
46
|
+
dependencyDigest = source.Checker
|
|
47
|
+
break
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if dependencyFile == "" {
|
|
51
|
+
t.Fatal("raw dependency source is absent from provenance")
|
|
52
|
+
}
|
|
53
|
+
for _, file := range session.graphStore.extractedFiles {
|
|
54
|
+
if file == dependencyFile {
|
|
55
|
+
t.Fatalf("raw dependency source was treated as an authored extraction: %v", session.graphStore.extractedFiles)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
dependencyShard, ok := session.graphStore.shards[session.graphStore.sourceKeys[dependencyFile]]
|
|
59
|
+
if !ok || dependencyShard.shard.Source == nil {
|
|
60
|
+
t.Fatalf("dependency provenance shard missing for %q", dependencyFile)
|
|
61
|
+
}
|
|
62
|
+
if len(dependencyShard.shard.Nodes) != 0 || len(dependencyShard.shard.Edges) != 0 {
|
|
63
|
+
t.Fatalf("dependency provenance shard owns graph facts: nodes=%v edges=%v", dependencyShard.shard.Nodes, dependencyShard.shard.Edges)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
boundaryFound := false
|
|
67
|
+
for _, node := range session.graphStore.nodes {
|
|
68
|
+
normalized := filepath.ToSlash(node.File)
|
|
69
|
+
if strings.Contains(normalized, "/node_modules/dep-src/") {
|
|
70
|
+
if !node.External || node.Name != "dependencyValue" {
|
|
71
|
+
t.Fatalf("unexpected dependency graph node: %+v", node)
|
|
72
|
+
}
|
|
73
|
+
boundaryFound = true
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if !boundaryFound {
|
|
77
|
+
t.Fatal("referenced dependency boundary leaf is absent")
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
writeGraphFile(t, dependencyPath, "export function dependencyValue(): number { return 2; }\nexport function dependencyInternal(): number { return dependencyValue(); }\n")
|
|
81
|
+
replacement, mode, changed, err := session.SnapshotShards()
|
|
82
|
+
if err != nil {
|
|
83
|
+
t.Fatal(err)
|
|
84
|
+
}
|
|
85
|
+
if replacement == nil {
|
|
86
|
+
t.Fatal("dependency edit did not publish a shard snapshot")
|
|
87
|
+
}
|
|
88
|
+
if mode != serveModeRebuild || !changed || len(replacement.Upserts) != len(replacement.Manifest) {
|
|
89
|
+
t.Fatalf(
|
|
90
|
+
"dependency edit should report and publish a complete rebuild: mode=%q changed=%v manifest=%d upserts=%d",
|
|
91
|
+
mode,
|
|
92
|
+
changed,
|
|
93
|
+
len(replacement.Manifest),
|
|
94
|
+
len(replacement.Upserts),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
dependencyDigestChanged := false
|
|
98
|
+
for _, source := range session.graphStore.provenance.Sources {
|
|
99
|
+
if source.File == dependencyFile && source.Checker != dependencyDigest {
|
|
100
|
+
dependencyDigestChanged = true
|
|
101
|
+
break
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if !dependencyDigestChanged {
|
|
105
|
+
t.Fatal("raw dependency edit did not refresh checker provenance")
|
|
106
|
+
}
|
|
107
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
108
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"reflect"
|
|
8
|
+
"sort"
|
|
9
|
+
"testing"
|
|
10
|
+
|
|
11
|
+
"github.com/samchon/ttsc/packages/ttsc/internal/graph"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// TestServeShardsMatchFullDumpOracle proves the initial transaction and one
|
|
15
|
+
// accepted edit reconstruct exactly the facts the complete builder produces.
|
|
16
|
+
// This keeps shard ownership and base-node reuse as an optimization boundary,
|
|
17
|
+
// never a second semantic contract.
|
|
18
|
+
func TestServeShardsMatchFullDumpOracle(t *testing.T) {
|
|
19
|
+
root := t.TempDir()
|
|
20
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
21
|
+
"compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
|
|
22
|
+
"include": ["src"]
|
|
23
|
+
}`)
|
|
24
|
+
writeGraphFile(t, filepath.Join(root, "src", "base.ts"), "export interface Base { run(): void; }\n")
|
|
25
|
+
writeGraphFile(t, filepath.Join(root, "src", "impl.ts"), "import { Base } from './base';\nexport class Impl implements Base { run(): void {} }\n")
|
|
26
|
+
writeGraphFile(t, filepath.Join(root, "src", "consumer.ts"), "import { Impl } from './impl';\nexport function consume(): void { new Impl().run(); }\n")
|
|
27
|
+
|
|
28
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
29
|
+
if err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
defer session.Close()
|
|
33
|
+
if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
|
|
34
|
+
t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
|
|
35
|
+
}
|
|
36
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
37
|
+
|
|
38
|
+
impl := filepath.Join(root, "src", "impl.ts")
|
|
39
|
+
if err := os.WriteFile(impl, []byte("import { Base } from './base';\nexport class Impl implements Base { run(): void { const touched = true; void touched; } }\n"), 0o644); err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
snapshot, mode, changed, err := session.SnapshotShards()
|
|
43
|
+
if err != nil {
|
|
44
|
+
t.Fatal(err)
|
|
45
|
+
}
|
|
46
|
+
if snapshot == nil || mode != serveModeIncremental || !changed {
|
|
47
|
+
t.Fatalf("edited shard snapshot = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
|
|
48
|
+
}
|
|
49
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
func assertServeShardFactsMatchFullDump(t *testing.T, session *graphSession) {
|
|
53
|
+
t.Helper()
|
|
54
|
+
complete, err := session.buildDump()
|
|
55
|
+
if err != nil {
|
|
56
|
+
t.Fatal(err)
|
|
57
|
+
}
|
|
58
|
+
var nodes []graph.DumpNode
|
|
59
|
+
var edges []graph.DumpEdge
|
|
60
|
+
var diagnostics []graph.Diagnostic
|
|
61
|
+
for _, committed := range session.graphStore.shards {
|
|
62
|
+
nodes = append(nodes, committed.shard.Nodes...)
|
|
63
|
+
edges = append(edges, committed.shard.Edges...)
|
|
64
|
+
diagnostics = append(diagnostics, committed.shard.Diagnostics...)
|
|
65
|
+
}
|
|
66
|
+
if !reflect.DeepEqual(canonicalJSONRows(nodes), canonicalJSONRows(complete.Nodes)) {
|
|
67
|
+
t.Fatal("committed shard nodes differ from complete-build oracle")
|
|
68
|
+
}
|
|
69
|
+
if !reflect.DeepEqual(canonicalJSONRows(edges), canonicalJSONRows(complete.Edges)) {
|
|
70
|
+
t.Fatal("committed shard edges differ from complete-build oracle")
|
|
71
|
+
}
|
|
72
|
+
if !reflect.DeepEqual(canonicalJSONRows(diagnostics), canonicalJSONRows(complete.Diagnostics)) {
|
|
73
|
+
t.Fatal("committed shard diagnostics differ from complete-build oracle")
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func canonicalJSONRows[T any](values []T) []string {
|
|
78
|
+
rows := make([]string, 0, len(values))
|
|
79
|
+
for _, value := range values {
|
|
80
|
+
encoded, err := json.Marshal(value)
|
|
81
|
+
if err != nil {
|
|
82
|
+
panic(err)
|
|
83
|
+
}
|
|
84
|
+
rows = append(rows, string(encoded))
|
|
85
|
+
}
|
|
86
|
+
sort.Strings(rows)
|
|
87
|
+
return rows
|
|
88
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServeShardsMergePendingAndNewEdits verifies a second disk edit cannot
|
|
10
|
+
// replace the invalidation closure of an earlier uncommitted generation. Both
|
|
11
|
+
// changed sources must enter the retry transaction before it can advance the
|
|
12
|
+
// shard-store base.
|
|
13
|
+
func TestServeShardsMergePendingAndNewEdits(t *testing.T) {
|
|
14
|
+
root := graphSessionFixture(t)
|
|
15
|
+
other := filepath.Join(root, "src", "other.ts")
|
|
16
|
+
writeGraphFile(t, other, "export class OtherBefore {}\n")
|
|
17
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
18
|
+
if err != nil {
|
|
19
|
+
t.Fatal(err)
|
|
20
|
+
}
|
|
21
|
+
defer session.Close()
|
|
22
|
+
if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
|
|
23
|
+
t.Fatalf("initial snapshot = snapshot:%v error:%v", snapshot != nil, err)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
first := filepath.Join(root, "src", "index.ts")
|
|
27
|
+
if err := os.WriteFile(first, []byte("export class FirstAfter {}\n"), 0o644); err != nil {
|
|
28
|
+
t.Fatal(err)
|
|
29
|
+
}
|
|
30
|
+
session.cwd = "relative-project"
|
|
31
|
+
if _, _, _, err := session.SnapshotShards(); err == nil {
|
|
32
|
+
t.Fatal("first edit unexpectedly committed through invalid project root")
|
|
33
|
+
}
|
|
34
|
+
if session.pending == nil {
|
|
35
|
+
t.Fatal("failed first edit left no pending invalidation")
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
session.cwd = root
|
|
39
|
+
if err := os.WriteFile(other, []byte("export class OtherAfter {}\n"), 0o644); err != nil {
|
|
40
|
+
t.Fatal(err)
|
|
41
|
+
}
|
|
42
|
+
recovered, mode, changed, err := session.SnapshotShards()
|
|
43
|
+
if err != nil {
|
|
44
|
+
t.Fatal(err)
|
|
45
|
+
}
|
|
46
|
+
if recovered == nil || mode != serveModeIncremental || !changed || session.pending != nil {
|
|
47
|
+
t.Fatalf("merged recovery = snapshot:%v mode:%q changed:%v pending:%v", recovered != nil, mode, changed, session.pending != nil)
|
|
48
|
+
}
|
|
49
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
50
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServeShardsPruneUnreferencedExternalEndpoints verifies an external leaf
|
|
10
|
+
// leaves both resident endpoint indexes when its last owning edge disappears.
|
|
11
|
+
func TestServeShardsPruneUnreferencedExternalEndpoints(t *testing.T) {
|
|
12
|
+
root := t.TempDir()
|
|
13
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
14
|
+
"compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
|
|
15
|
+
"include": ["src"]
|
|
16
|
+
}`)
|
|
17
|
+
writeGraphFile(t, filepath.Join(root, "src", "api.d.ts"), "export declare function external(): number;\n")
|
|
18
|
+
index := filepath.Join(root, "src", "index.ts")
|
|
19
|
+
writeGraphFile(t, index, "import { external } from './api';\nexport function run(): number { return external(); }\n")
|
|
20
|
+
|
|
21
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
22
|
+
if err != nil {
|
|
23
|
+
t.Fatal(err)
|
|
24
|
+
}
|
|
25
|
+
defer session.Close()
|
|
26
|
+
if snapshot, _, _, err := session.SnapshotShards(); err != nil || snapshot == nil {
|
|
27
|
+
t.Fatalf("initial shard snapshot = snapshot:%v error:%v", snapshot != nil, err)
|
|
28
|
+
}
|
|
29
|
+
source := session.compiler.Program().SourceFile(index)
|
|
30
|
+
if source == nil {
|
|
31
|
+
t.Fatal("fixture source was absent from resident program")
|
|
32
|
+
}
|
|
33
|
+
sourceKey := session.graphStore.sourceKeys[source.FileName()]
|
|
34
|
+
var externalID string
|
|
35
|
+
for id := range session.graphStore.sourceExternal[sourceKey] {
|
|
36
|
+
externalID = id
|
|
37
|
+
break
|
|
38
|
+
}
|
|
39
|
+
if externalID == "" {
|
|
40
|
+
t.Fatal("fixture did not produce an external endpoint")
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if err := os.WriteFile(index, []byte("import { external } from './api';\nexport function run(): number { return 1; }\n"), 0o644); err != nil {
|
|
44
|
+
t.Fatal(err)
|
|
45
|
+
}
|
|
46
|
+
snapshot, mode, changed, err := session.SnapshotShards()
|
|
47
|
+
if err != nil {
|
|
48
|
+
t.Fatal(err)
|
|
49
|
+
}
|
|
50
|
+
if snapshot == nil || mode != serveModeIncremental || !changed {
|
|
51
|
+
t.Fatalf("external endpoint edit = snapshot:%v mode:%q changed:%v", snapshot != nil, mode, changed)
|
|
52
|
+
}
|
|
53
|
+
if session.graphStore.externalReferences[externalID] != 0 {
|
|
54
|
+
t.Fatalf("external endpoint %s retained a reference count", externalID)
|
|
55
|
+
}
|
|
56
|
+
if _, exists := session.graphStore.externalNodes[externalID]; exists {
|
|
57
|
+
t.Fatalf("external endpoint %s remained in the wire endpoint cache", externalID)
|
|
58
|
+
}
|
|
59
|
+
for id, node := range session.graphStore.nodes {
|
|
60
|
+
if node.External {
|
|
61
|
+
t.Fatalf("external endpoint %s remained in the internal endpoint cache as %s", externalID, id)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
65
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"path/filepath"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// TestServeShardsPublishCreatedSource verifies a source that appears while the
|
|
9
|
+
// session is resident enters the generation as its own shard and unblocks the
|
|
10
|
+
// dependents that could not resolve it before.
|
|
11
|
+
//
|
|
12
|
+
// Deletion is proven explicitly, but its mirror is the case where a wrong
|
|
13
|
+
// closure is invisible: the consumer holds a graph that simply lacks a file,
|
|
14
|
+
// and an agent asking about it is told the symbol does not exist rather than
|
|
15
|
+
// receiving an error. The dependent must also refresh, because the import that
|
|
16
|
+
// previously failed to resolve now produces real cross-file facts.
|
|
17
|
+
//
|
|
18
|
+
// 1. Commit a project whose consumer imports a module that does not exist yet.
|
|
19
|
+
// 2. Create that module and request another shard snapshot.
|
|
20
|
+
// 3. Require a shard for the new source and a replaced shard for its dependent.
|
|
21
|
+
func TestServeShardsPublishCreatedSource(t *testing.T) {
|
|
22
|
+
root := t.TempDir()
|
|
23
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
24
|
+
"compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
|
|
25
|
+
"include": ["src"]
|
|
26
|
+
}`)
|
|
27
|
+
consumerFile := filepath.Join(root, "src", "consumer.ts")
|
|
28
|
+
writeGraphFile(t, consumerFile, "import { later } from './later';\nexport function consume(): number { return later(); }\n")
|
|
29
|
+
|
|
30
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
31
|
+
if err != nil {
|
|
32
|
+
t.Fatal(err)
|
|
33
|
+
}
|
|
34
|
+
defer session.Close()
|
|
35
|
+
initial, mode, changed, err := session.SnapshotShards()
|
|
36
|
+
if err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
if initial == nil || mode != serveModeInitial || !changed {
|
|
40
|
+
t.Fatalf("initial snapshot = snapshot:%v mode:%q changed:%v", initial != nil, mode, changed)
|
|
41
|
+
}
|
|
42
|
+
consumerSource := session.compiler.Program().SourceFile(consumerFile)
|
|
43
|
+
if consumerSource == nil {
|
|
44
|
+
t.Fatal("fixture source was absent from resident program")
|
|
45
|
+
}
|
|
46
|
+
consumerKeyFile := consumerSource.FileName()
|
|
47
|
+
initialConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
|
|
48
|
+
if initialConsumerKey == "" {
|
|
49
|
+
t.Fatal("initial generation omitted the dependent source shard")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
createdFile := filepath.Join(root, "src", "later.ts")
|
|
53
|
+
writeGraphFile(t, createdFile, "export function later(): number { return 7; }\n")
|
|
54
|
+
|
|
55
|
+
next, mode, changed, err := session.SnapshotShards()
|
|
56
|
+
if err != nil {
|
|
57
|
+
t.Fatal(err)
|
|
58
|
+
}
|
|
59
|
+
if next == nil || !changed || next.BaseGeneration != initial.Generation {
|
|
60
|
+
t.Fatalf("created-source generation = snapshot:%#v mode:%q changed:%v", next, mode, changed)
|
|
61
|
+
}
|
|
62
|
+
createdSource := session.compiler.Program().SourceFile(createdFile)
|
|
63
|
+
if createdSource == nil {
|
|
64
|
+
t.Fatal("created source did not enter the resident program")
|
|
65
|
+
}
|
|
66
|
+
createdKey := session.graphStore.sourceKeys[createdSource.FileName()]
|
|
67
|
+
if createdKey == "" {
|
|
68
|
+
t.Fatal("created source did not acquire a committed shard identity")
|
|
69
|
+
}
|
|
70
|
+
if !containsUpsertedShardKey(next, createdKey) {
|
|
71
|
+
t.Fatalf("generation did not publish the created source shard %q", createdKey)
|
|
72
|
+
}
|
|
73
|
+
// The dependent's import resolved for the first time, so its cross-file facts
|
|
74
|
+
// changed even though its own text did not. Reusing its prior shard would
|
|
75
|
+
// publish a consumer graph with a call edge to nothing.
|
|
76
|
+
nextConsumerKey := session.graphStore.sourceKeys[consumerKeyFile]
|
|
77
|
+
if nextConsumerKey == initialConsumerKey {
|
|
78
|
+
t.Fatalf("dependent shard identity %q survived a newly resolved import", nextConsumerKey)
|
|
79
|
+
}
|
|
80
|
+
if !containsUpsertedShardKey(next, nextConsumerKey) {
|
|
81
|
+
t.Fatalf("generation did not republish the dependent shard %q", nextConsumerKey)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
func containsUpsertedShardKey(snapshot *serveGraphSnapshot, expected string) bool {
|
|
86
|
+
for _, upsert := range snapshot.Upserts {
|
|
87
|
+
if upsert.Shard.Key == expected {
|
|
88
|
+
return true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"testing"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
// TestServeShardsRebuildDeclarationEdit verifies that a declaration-file
|
|
10
|
+
// movement publishes one honest complete replacement instead of entering the
|
|
11
|
+
// authored-source partial builder with an empty invalidation set.
|
|
12
|
+
func TestServeShardsRebuildDeclarationEdit(t *testing.T) {
|
|
13
|
+
root := t.TempDir()
|
|
14
|
+
writeGraphFile(t, filepath.Join(root, "tsconfig.json"), `{
|
|
15
|
+
"compilerOptions": { "target": "ES2022", "module": "commonjs", "strict": true },
|
|
16
|
+
"include": ["src"]
|
|
17
|
+
}`)
|
|
18
|
+
declaration := filepath.Join(root, "src", "api.d.ts")
|
|
19
|
+
writeGraphFile(t, declaration, "export declare function external(): number;\n")
|
|
20
|
+
writeGraphFile(t, filepath.Join(root, "src", "index.ts"), "import { external } from './api';\nexport const value = external();\n")
|
|
21
|
+
|
|
22
|
+
session, err := newGraphSession(root, "tsconfig.json")
|
|
23
|
+
if err != nil {
|
|
24
|
+
t.Fatal(err)
|
|
25
|
+
}
|
|
26
|
+
defer session.Close()
|
|
27
|
+
initial, _, _, err := session.SnapshotShards()
|
|
28
|
+
if err != nil || initial == nil {
|
|
29
|
+
t.Fatalf("initial shard snapshot = snapshot:%v error:%v", initial != nil, err)
|
|
30
|
+
}
|
|
31
|
+
if err := os.WriteFile(declaration, []byte("export declare function external(): string;\n"), 0o644); err != nil {
|
|
32
|
+
t.Fatal(err)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
replacement, mode, changed, err := session.SnapshotShards()
|
|
36
|
+
if err != nil {
|
|
37
|
+
t.Fatal(err)
|
|
38
|
+
}
|
|
39
|
+
if replacement == nil || mode != serveModeRebuild || !changed || replacement.BaseGeneration != initial.Generation {
|
|
40
|
+
t.Fatalf("declaration edit = snapshot:%#v mode:%q changed:%v", replacement, mode, changed)
|
|
41
|
+
}
|
|
42
|
+
assertServeShardFactsMatchFullDump(t, session)
|
|
43
|
+
}
|