seer-mcp 0.1.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/.vscode/settings.json +3 -0
- package/LICENSE +176 -0
- package/README.md +272 -0
- package/README_dev.md +199 -0
- package/dist/bundle/ci.d.ts +47 -0
- package/dist/bundle/ci.d.ts.map +1 -0
- package/dist/bundle/ci.js +113 -0
- package/dist/bundle/ci.js.map +1 -0
- package/dist/bundle/contract.d.ts +111 -0
- package/dist/bundle/contract.d.ts.map +1 -0
- package/dist/bundle/contract.js +352 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/export.d.ts +36 -0
- package/dist/bundle/export.d.ts.map +1 -0
- package/dist/bundle/export.js +152 -0
- package/dist/bundle/export.js.map +1 -0
- package/dist/bundle/external.d.ts +66 -0
- package/dist/bundle/external.d.ts.map +1 -0
- package/dist/bundle/external.js +238 -0
- package/dist/bundle/external.js.map +1 -0
- package/dist/bundle/format.d.ts +94 -0
- package/dist/bundle/format.d.ts.map +1 -0
- package/dist/bundle/format.js +42 -0
- package/dist/bundle/format.js.map +1 -0
- package/dist/bundle/import.d.ts +49 -0
- package/dist/bundle/import.d.ts.map +1 -0
- package/dist/bundle/import.js +116 -0
- package/dist/bundle/import.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1402 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +48 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +284 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +616 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/store.d.ts +1011 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +3888 -0
- package/dist/db/store.js.map +1 -0
- package/dist/graph/pagerank.d.ts +9 -0
- package/dist/graph/pagerank.d.ts.map +1 -0
- package/dist/graph/pagerank.js +47 -0
- package/dist/graph/pagerank.js.map +1 -0
- package/dist/indexer/architecture.d.ts +72 -0
- package/dist/indexer/architecture.d.ts.map +1 -0
- package/dist/indexer/architecture.js +112 -0
- package/dist/indexer/architecture.js.map +1 -0
- package/dist/indexer/behavior.d.ts +75 -0
- package/dist/indexer/behavior.d.ts.map +1 -0
- package/dist/indexer/behavior.js +395 -0
- package/dist/indexer/behavior.js.map +1 -0
- package/dist/indexer/boundaries.d.ts +60 -0
- package/dist/indexer/boundaries.d.ts.map +1 -0
- package/dist/indexer/boundaries.js +366 -0
- package/dist/indexer/boundaries.js.map +1 -0
- package/dist/indexer/churn.d.ts +15 -0
- package/dist/indexer/churn.d.ts.map +1 -0
- package/dist/indexer/churn.js +49 -0
- package/dist/indexer/churn.js.map +1 -0
- package/dist/indexer/classify.d.ts +9 -0
- package/dist/indexer/classify.d.ts.map +1 -0
- package/dist/indexer/classify.js +90 -0
- package/dist/indexer/classify.js.map +1 -0
- package/dist/indexer/context.d.ts +176 -0
- package/dist/indexer/context.d.ts.map +1 -0
- package/dist/indexer/context.js +193 -0
- package/dist/indexer/context.js.map +1 -0
- package/dist/indexer/continuity.d.ts +67 -0
- package/dist/indexer/continuity.d.ts.map +1 -0
- package/dist/indexer/continuity.js +288 -0
- package/dist/indexer/continuity.js.map +1 -0
- package/dist/indexer/detectchanges.d.ts +32 -0
- package/dist/indexer/detectchanges.d.ts.map +1 -0
- package/dist/indexer/detectchanges.js +74 -0
- package/dist/indexer/detectchanges.js.map +1 -0
- package/dist/indexer/discovery.d.ts +37 -0
- package/dist/indexer/discovery.d.ts.map +1 -0
- package/dist/indexer/discovery.js +136 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/externaldeps.d.ts +18 -0
- package/dist/indexer/externaldeps.d.ts.map +1 -0
- package/dist/indexer/externaldeps.js +288 -0
- package/dist/indexer/externaldeps.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +128 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/git.d.ts +144 -0
- package/dist/indexer/git.d.ts.map +1 -0
- package/dist/indexer/git.js +444 -0
- package/dist/indexer/git.js.map +1 -0
- package/dist/indexer/index.d.ts +145 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +930 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/modules.d.ts +62 -0
- package/dist/indexer/modules.d.ts.map +1 -0
- package/dist/indexer/modules.js +293 -0
- package/dist/indexer/modules.js.map +1 -0
- package/dist/indexer/preflight.d.ts +154 -0
- package/dist/indexer/preflight.d.ts.map +1 -0
- package/dist/indexer/preflight.js +399 -0
- package/dist/indexer/preflight.js.map +1 -0
- package/dist/indexer/protoScanner.d.ts +34 -0
- package/dist/indexer/protoScanner.d.ts.map +1 -0
- package/dist/indexer/protoScanner.js +133 -0
- package/dist/indexer/protoScanner.js.map +1 -0
- package/dist/indexer/risk.d.ts +115 -0
- package/dist/indexer/risk.d.ts.map +1 -0
- package/dist/indexer/risk.js +194 -0
- package/dist/indexer/risk.js.map +1 -0
- package/dist/indexer/serviceHostScanner.d.ts +25 -0
- package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
- package/dist/indexer/serviceHostScanner.js +95 -0
- package/dist/indexer/serviceHostScanner.js.map +1 -0
- package/dist/indexer/serviceLinks.d.ts +105 -0
- package/dist/indexer/serviceLinks.d.ts.map +1 -0
- package/dist/indexer/serviceLinks.js +509 -0
- package/dist/indexer/serviceLinks.js.map +1 -0
- package/dist/indexer/shapehash.d.ts +98 -0
- package/dist/indexer/shapehash.d.ts.map +1 -0
- package/dist/indexer/shapehash.js +354 -0
- package/dist/indexer/shapehash.js.map +1 -0
- package/dist/indexer/skeleton.d.ts +15 -0
- package/dist/indexer/skeleton.d.ts.map +1 -0
- package/dist/indexer/skeleton.js +136 -0
- package/dist/indexer/skeleton.js.map +1 -0
- package/dist/indexer/symbolhistory.d.ts +41 -0
- package/dist/indexer/symbolhistory.d.ts.map +1 -0
- package/dist/indexer/symbolhistory.js +124 -0
- package/dist/indexer/symbolhistory.js.map +1 -0
- package/dist/indexer/watcher.d.ts +68 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +179 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/server.d.ts +80 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1610 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/parser/index.d.ts +8 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +33 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/languages/cpp.d.ts +3 -0
- package/dist/parser/languages/cpp.d.ts.map +1 -0
- package/dist/parser/languages/cpp.js +350 -0
- package/dist/parser/languages/cpp.js.map +1 -0
- package/dist/parser/languages/csharp.d.ts +3 -0
- package/dist/parser/languages/csharp.d.ts.map +1 -0
- package/dist/parser/languages/csharp.js +239 -0
- package/dist/parser/languages/csharp.js.map +1 -0
- package/dist/parser/languages/go.d.ts +3 -0
- package/dist/parser/languages/go.d.ts.map +1 -0
- package/dist/parser/languages/go.js +259 -0
- package/dist/parser/languages/go.js.map +1 -0
- package/dist/parser/languages/java.d.ts +3 -0
- package/dist/parser/languages/java.d.ts.map +1 -0
- package/dist/parser/languages/java.js +391 -0
- package/dist/parser/languages/java.js.map +1 -0
- package/dist/parser/languages/python.d.ts +3 -0
- package/dist/parser/languages/python.d.ts.map +1 -0
- package/dist/parser/languages/python.js +396 -0
- package/dist/parser/languages/python.js.map +1 -0
- package/dist/parser/languages/rust.d.ts +3 -0
- package/dist/parser/languages/rust.d.ts.map +1 -0
- package/dist/parser/languages/rust.js +159 -0
- package/dist/parser/languages/rust.js.map +1 -0
- package/dist/parser/languages/typescript.d.ts +3 -0
- package/dist/parser/languages/typescript.d.ts.map +1 -0
- package/dist/parser/languages/typescript.js +1442 -0
- package/dist/parser/languages/typescript.js.map +1 -0
- package/dist/parser/parserContext.d.ts +77 -0
- package/dist/parser/parserContext.d.ts.map +1 -0
- package/dist/parser/parserContext.js +354 -0
- package/dist/parser/parserContext.js.map +1 -0
- package/dist/parser/walker.d.ts +81 -0
- package/dist/parser/walker.d.ts.map +1 -0
- package/dist/parser/walker.js +217 -0
- package/dist/parser/walker.js.map +1 -0
- package/dist/parser/worker.d.ts +66 -0
- package/dist/parser/worker.d.ts.map +1 -0
- package/dist/parser/worker.js +129 -0
- package/dist/parser/worker.js.map +1 -0
- package/dist/parser/workerpool.d.ts +107 -0
- package/dist/parser/workerpool.d.ts.map +1 -0
- package/dist/parser/workerpool.js +383 -0
- package/dist/parser/workerpool.js.map +1 -0
- package/dist/scip/format.d.ts +87 -0
- package/dist/scip/format.d.ts.map +1 -0
- package/dist/scip/format.js +31 -0
- package/dist/scip/format.js.map +1 -0
- package/dist/scip/import.d.ts +37 -0
- package/dist/scip/import.d.ts.map +1 -0
- package/dist/scip/import.js +180 -0
- package/dist/scip/import.js.map +1 -0
- package/dist/types.d.ts +392 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/docs/architecture.md +105 -0
- package/docs/benchmarks/methodology.md +134 -0
- package/docs/benchmarks/raw-results.md +71 -0
- package/docs/benchmarks.md +74 -0
- package/docs/cli.md +148 -0
- package/docs/examples/behavior-tests.md +70 -0
- package/docs/examples/change-history.md +85 -0
- package/docs/examples/pre-edit-context.md +81 -0
- package/docs/examples/service-links.md +88 -0
- package/docs/examples.md +80 -0
- package/docs/faq.md +70 -0
- package/docs/internals.md +104 -0
- package/docs/languages.md +70 -0
- package/docs/limits.md +52 -0
- package/docs/mcp.md +199 -0
- package/docs/quickstart.md +119 -0
- package/docs/testing.md +123 -0
- package/docs/tools.md +115 -0
- package/package.json +52 -0
- package/research-codebase.md +578 -0
- package/seer-cli-docs.md +326 -0
- package/seer-master-guide.md +246 -0
- package/src/bundle/ci.ts +141 -0
- package/src/bundle/contract.ts +387 -0
- package/src/bundle/export.ts +175 -0
- package/src/bundle/external.ts +285 -0
- package/src/bundle/format.ts +92 -0
- package/src/bundle/import.ts +157 -0
- package/src/cli/index.ts +1249 -0
- package/src/cli/init.ts +389 -0
- package/src/db/schema.ts +614 -0
- package/src/db/store.ts +4306 -0
- package/src/graph/pagerank.ts +53 -0
- package/src/indexer/architecture.ts +148 -0
- package/src/indexer/behavior.ts +466 -0
- package/src/indexer/boundaries.ts +374 -0
- package/src/indexer/churn.ts +58 -0
- package/src/indexer/classify.ts +96 -0
- package/src/indexer/context.ts +340 -0
- package/src/indexer/continuity.ts +322 -0
- package/src/indexer/detectchanges.ts +94 -0
- package/src/indexer/discovery.ts +176 -0
- package/src/indexer/externaldeps.ts +243 -0
- package/src/indexer/freshness.ts +166 -0
- package/src/indexer/git.ts +453 -0
- package/src/indexer/index.ts +1092 -0
- package/src/indexer/modules.ts +358 -0
- package/src/indexer/preflight.ts +548 -0
- package/src/indexer/protoScanner.ts +147 -0
- package/src/indexer/risk.ts +304 -0
- package/src/indexer/serviceHostScanner.ts +92 -0
- package/src/indexer/serviceLinks.ts +543 -0
- package/src/indexer/shapehash.ts +370 -0
- package/src/indexer/skeleton.ts +169 -0
- package/src/indexer/symbolhistory.ts +172 -0
- package/src/indexer/watcher.ts +206 -0
- package/src/mcp/server.ts +1659 -0
- package/src/parser/index.ts +37 -0
- package/src/parser/languages/cpp.ts +361 -0
- package/src/parser/languages/csharp.ts +235 -0
- package/src/parser/languages/go.ts +259 -0
- package/src/parser/languages/java.ts +382 -0
- package/src/parser/languages/python.ts +370 -0
- package/src/parser/languages/rust.ts +164 -0
- package/src/parser/languages/typescript.ts +1435 -0
- package/src/parser/parserContext.ts +392 -0
- package/src/parser/walker.ts +306 -0
- package/src/parser/worker.ts +181 -0
- package/src/parser/workerpool.ts +448 -0
- package/src/scip/format.ts +83 -0
- package/src/scip/import.ts +216 -0
- package/src/types.ts +457 -0
- package/tests/benchmark-service-links.ts +244 -0
- package/tests/bug-regressions.ts +626 -0
- package/tests/filters.ts +264 -0
- package/tests/fixtures/Counter.tsx +38 -0
- package/tests/fixtures/caller.ts +7 -0
- package/tests/fixtures/collisions.ts +23 -0
- package/tests/fixtures/local_helper.ts +5 -0
- package/tests/fixtures/overloads.java +17 -0
- package/tests/fixtures/remote_helper.ts +4 -0
- package/tests/fixtures/sample.c +15 -0
- package/tests/fixtures/sample.cpp +47 -0
- package/tests/fixtures/sample.cs +62 -0
- package/tests/fixtures/sample.go +68 -0
- package/tests/fixtures/sample.h +30 -0
- package/tests/fixtures/sample.java +85 -0
- package/tests/fixtures/sample.py +46 -0
- package/tests/fixtures/sample.rs +78 -0
- package/tests/fixtures/sample.ts +76 -0
- package/tests/fixtures-service/HttpClients.cs +30 -0
- package/tests/fixtures-service/HttpClients.java +24 -0
- package/tests/fixtures-service/billing.ts +15 -0
- package/tests/fixtures-service/docker-compose.yml +15 -0
- package/tests/fixtures-service/gateway.ts +10 -0
- package/tests/fixtures-service/get_user.ts +11 -0
- package/tests/fixtures-service/graphql_client.ts +63 -0
- package/tests/fixtures-service/graphql_server.ts +30 -0
- package/tests/fixtures-service/grpc_client.go +30 -0
- package/tests/fixtures-service/http_clients.go +23 -0
- package/tests/fixtures-service/http_clients.py +38 -0
- package/tests/fixtures-service/http_clients.ts +49 -0
- package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
- package/tests/fixtures-service/k8s_calls.ts +20 -0
- package/tests/fixtures-service/messaging.ts +87 -0
- package/tests/fixtures-service/trpc_client.ts +39 -0
- package/tests/fixtures-service/trpc_server.ts +39 -0
- package/tests/fixtures-service/user_service.proto +33 -0
- package/tests/fixtures-trackcd/Cargo.toml +11 -0
- package/tests/fixtures-trackcd/SpringController.java +36 -0
- package/tests/fixtures-trackcd/auth_service.ts +19 -0
- package/tests/fixtures-trackcd/complex_module.py +50 -0
- package/tests/fixtures-trackcd/express_app.js +30 -0
- package/tests/fixtures-trackcd/fastapi_app.py +49 -0
- package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
- package/tests/fixtures-trackcd/go.mod +8 -0
- package/tests/fixtures-trackcd/package.json +15 -0
- package/tests/fixtures-trackcd/requirements.txt +4 -0
- package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
- package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
- package/tests/fixtures-tracke/auth/crypto.ts +7 -0
- package/tests/fixtures-tracke/billing/Billing.ts +20 -0
- package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
- package/tests/fixtures-tracke/billing/server.ts +17 -0
- package/tests/fixtures-tracke/package.json +7 -0
- package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
- package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
- package/tests/fixtures-trackf/package.json +5 -0
- package/tests/fixtures-trackf/src/auth.ts +26 -0
- package/tests/fixtures-trackf/src/handlers.ts +35 -0
- package/tests/fixtures-tracki/billing/routes.ts +12 -0
- package/tests/fixtures-tracki/gateway/client.ts +13 -0
- package/tests/git-features.ts +267 -0
- package/tests/init.ts +141 -0
- package/tests/mcp-jit.ts +130 -0
- package/tests/mcp-smoke.ts +191 -0
- package/tests/mcp-trackcd.ts +169 -0
- package/tests/mcp-tracke.ts +229 -0
- package/tests/mcp-trackf.ts +330 -0
- package/tests/mcp-trackg.ts +219 -0
- package/tests/mcp-tracki.ts +174 -0
- package/tests/mcp-watcher.ts +126 -0
- package/tests/optspec.ts +194 -0
- package/tests/parallel-index.ts +333 -0
- package/tests/parallel-read.ts +125 -0
- package/tests/parallel-recovery.ts +241 -0
- package/tests/perf-callers.ts +145 -0
- package/tests/query-parity.ts +184 -0
- package/tests/query-perf.ts +55 -0
- package/tests/scale-parallel-parity.ts +225 -0
- package/tests/scale-test.ts +523 -0
- package/tests/smoke.ts +396 -0
- package/tests/trackcd.ts +325 -0
- package/tests/tracke-collisions.ts +255 -0
- package/tests/tracke.ts +314 -0
- package/tests/trackf-bugs.ts +406 -0
- package/tests/trackf.ts +390 -0
- package/tests/trackg.ts +1372 -0
- package/tests/tracki-boundaries.ts +202 -0
- package/tests/tracki-continuity.ts +253 -0
- package/tests/tracki-contract-diff.ts +249 -0
- package/tests/tracki-external-bundles.ts +341 -0
- package/tests/tracki-preflight.ts +251 -0
- package/tests/verify-roles.ts +51 -0
- package/tests/worker-parity.ts +286 -0
- package/tests/worker-pool.ts +262 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parallel-indexing parity test (Step 4 of parallel parsing).
|
|
3
|
+
*
|
|
4
|
+
* The contract: indexing the same workspace with `parallel: true` must
|
|
5
|
+
* produce a DB byte-equivalent to the serial path. The check is row-level,
|
|
6
|
+
* not count-level — counts can match while routes/config-key/symbol_role
|
|
7
|
+
* resolution silently diverges.
|
|
8
|
+
*
|
|
9
|
+
* Covered:
|
|
10
|
+
* - Per-table row diffs (files, symbols, edges, file_imports, routes,
|
|
11
|
+
* config_keys, external_dependencies, FTS hits).
|
|
12
|
+
* - Top-K PageRank symbol IDs and names match.
|
|
13
|
+
* - jobs ∈ {1, 2, 4, 8} all produce the same DB as serial.
|
|
14
|
+
* - Cache-hit re-index: a second parallel pass over an unchanged tree
|
|
15
|
+
* reports `indexed=0`, `reusedFromCache=N`, `pagerankRecomputed=false`,
|
|
16
|
+
* and the DB is identical to the first pass.
|
|
17
|
+
* - One-file edit: only the edited file's row changes; PageRank recomputes.
|
|
18
|
+
* - Stale-file pruning: a deleted file is removed (FK cascade clears
|
|
19
|
+
* symbols/edges/imports/routes/config_keys).
|
|
20
|
+
*
|
|
21
|
+
* Run with: npm run test:parallel-index
|
|
22
|
+
*/
|
|
23
|
+
import fs from 'fs';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import os from 'os';
|
|
26
|
+
import { Indexer } from '../src/indexer/index';
|
|
27
|
+
import { Store } from '../src/db/store';
|
|
28
|
+
|
|
29
|
+
const FIXTURES_DIR = path.join(__dirname, 'fixtures');
|
|
30
|
+
const FIXTURES_TRACKCD = path.join(__dirname, 'fixtures-trackcd');
|
|
31
|
+
|
|
32
|
+
let passed = 0;
|
|
33
|
+
let failed = 0;
|
|
34
|
+
|
|
35
|
+
function assert(condition: boolean, message: string): void {
|
|
36
|
+
if (condition) {
|
|
37
|
+
console.log(` ✓ ${message}`);
|
|
38
|
+
passed++;
|
|
39
|
+
} else {
|
|
40
|
+
console.error(` ✗ ${message}`);
|
|
41
|
+
failed++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Fixture set up ──────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Stage a temp workspace combining smoke + trackcd fixtures so we exercise:
|
|
49
|
+
* - all 9 language extractors (smoke fixtures)
|
|
50
|
+
* - routes, config keys, external dependencies (trackcd fixtures)
|
|
51
|
+
* - the C/C++ declaration-vs-definition split (sample.h + sample.cpp)
|
|
52
|
+
* - test-file classification (tests/ subdir from trackcd)
|
|
53
|
+
*/
|
|
54
|
+
function stageFixtures(): string {
|
|
55
|
+
const root = path.join(os.tmpdir(), `seer-parallel-fixtures-${Date.now()}`);
|
|
56
|
+
fs.mkdirSync(root, { recursive: true });
|
|
57
|
+
for (const f of fs.readdirSync(FIXTURES_DIR)) {
|
|
58
|
+
const src = path.join(FIXTURES_DIR, f);
|
|
59
|
+
if (fs.statSync(src).isFile()) fs.copyFileSync(src, path.join(root, f));
|
|
60
|
+
}
|
|
61
|
+
// Bring in select trackcd fixtures (avoid name clashes by prefixing).
|
|
62
|
+
for (const f of fs.readdirSync(FIXTURES_TRACKCD)) {
|
|
63
|
+
const src = path.join(FIXTURES_TRACKCD, f);
|
|
64
|
+
if (fs.statSync(src).isFile()) {
|
|
65
|
+
fs.copyFileSync(src, path.join(root, `trackcd_${f}`));
|
|
66
|
+
} else if (fs.statSync(src).isDirectory() && f === 'tests') {
|
|
67
|
+
fs.mkdirSync(path.join(root, 'tests'), { recursive: true });
|
|
68
|
+
for (const sub of fs.readdirSync(src)) {
|
|
69
|
+
fs.copyFileSync(path.join(src, sub), path.join(root, 'tests', sub));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return root;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── DB dump helpers ─────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
interface DbSnapshot {
|
|
79
|
+
files: unknown[];
|
|
80
|
+
symbols: unknown[];
|
|
81
|
+
edges: unknown[];
|
|
82
|
+
file_imports: unknown[];
|
|
83
|
+
routes: unknown[];
|
|
84
|
+
config_keys: unknown[];
|
|
85
|
+
external_dependencies: unknown[];
|
|
86
|
+
fts_symbols_validate: unknown[];
|
|
87
|
+
fts_files_auth: unknown[];
|
|
88
|
+
pagerank_top: unknown[];
|
|
89
|
+
role_counts: unknown;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function dumpDb(store: Store): DbSnapshot {
|
|
93
|
+
const db = store.rawDb();
|
|
94
|
+
// `indexed_at` excluded — it's wall-clock, expected to differ across runs.
|
|
95
|
+
// Everything else is content-derived and must match exactly.
|
|
96
|
+
return {
|
|
97
|
+
files: db.prepare(`
|
|
98
|
+
SELECT id, rel_path, language, hash, lines, role, is_vendor, is_generated
|
|
99
|
+
FROM files ORDER BY rel_path
|
|
100
|
+
`).all(),
|
|
101
|
+
symbols: db.prepare(`
|
|
102
|
+
SELECT id, name, qualified_name, kind, file_id, line_start, line_end,
|
|
103
|
+
col_start, col_end, signature, is_rankable,
|
|
104
|
+
loc, cyclomatic, cognitive, max_nesting, symbol_key, symbol_role
|
|
105
|
+
FROM symbols ORDER BY file_id, line_start, line_end, name, qualified_name
|
|
106
|
+
`).all(),
|
|
107
|
+
edges: db.prepare(`
|
|
108
|
+
SELECT from_id, to_id, to_name, kind, line FROM edges
|
|
109
|
+
ORDER BY from_id, line, to_name, kind
|
|
110
|
+
`).all(),
|
|
111
|
+
file_imports: db.prepare(`
|
|
112
|
+
SELECT from_file_id, import_name, resolved_file_id FROM file_imports
|
|
113
|
+
ORDER BY from_file_id, import_name
|
|
114
|
+
`).all(),
|
|
115
|
+
routes: db.prepare(`
|
|
116
|
+
SELECT file_id, method, path, framework, handler_name, handler_id, line
|
|
117
|
+
FROM routes ORDER BY file_id, line, method, path
|
|
118
|
+
`).all(),
|
|
119
|
+
config_keys: db.prepare(`
|
|
120
|
+
SELECT key, source, file_id, symbol_id, line
|
|
121
|
+
FROM config_keys ORDER BY file_id, line, key
|
|
122
|
+
`).all(),
|
|
123
|
+
external_dependencies: db.prepare(`
|
|
124
|
+
SELECT name, version_range, ecosystem, manifest_path, is_dev FROM external_dependencies
|
|
125
|
+
ORDER BY ecosystem, name, manifest_path
|
|
126
|
+
`).all(),
|
|
127
|
+
// Two representative FTS queries — verify BM25-ranked hits match.
|
|
128
|
+
fts_symbols_validate: store.searchSymbolsFts('validate', { limit: 20 })
|
|
129
|
+
.map(r => ({ name: r.name, qualifiedName: r.qualifiedName, filePath: r.filePath, lineStart: r.lineStart })),
|
|
130
|
+
fts_files_auth: store.searchFilesFts('auth', 20)
|
|
131
|
+
.map(r => ({ relPath: r.relPath, language: r.language })),
|
|
132
|
+
// Top PageRank rows. ID, name, kind, filePath together — order must match.
|
|
133
|
+
pagerank_top: db.prepare(`
|
|
134
|
+
SELECT s.id, s.name, s.qualified_name, s.kind, f.rel_path, s.pagerank
|
|
135
|
+
FROM symbols s JOIN files f ON f.id = s.file_id
|
|
136
|
+
WHERE s.is_rankable = 1
|
|
137
|
+
ORDER BY s.pagerank DESC, s.id ASC
|
|
138
|
+
LIMIT 50
|
|
139
|
+
`).all(),
|
|
140
|
+
role_counts: store.getRoleCounts(),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function jcanon(v: unknown): string {
|
|
145
|
+
return JSON.stringify(v, (_k, x) => {
|
|
146
|
+
if (x && typeof x === 'object' && !Array.isArray(x)) {
|
|
147
|
+
const sorted: Record<string, unknown> = {};
|
|
148
|
+
for (const k of Object.keys(x as Record<string, unknown>).sort()) {
|
|
149
|
+
sorted[k] = (x as Record<string, unknown>)[k];
|
|
150
|
+
}
|
|
151
|
+
return sorted;
|
|
152
|
+
}
|
|
153
|
+
return x;
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function diffSnapshots(a: DbSnapshot, b: DbSnapshot): string[] {
|
|
158
|
+
const out: string[] = [];
|
|
159
|
+
for (const k of Object.keys(a) as Array<keyof DbSnapshot>) {
|
|
160
|
+
const sa = jcanon(a[k]);
|
|
161
|
+
const sb = jcanon(b[k]);
|
|
162
|
+
if (sa !== sb) {
|
|
163
|
+
const lenA = Array.isArray(a[k]) ? (a[k] as unknown[]).length : 1;
|
|
164
|
+
const lenB = Array.isArray(b[k]) ? (b[k] as unknown[]).length : 1;
|
|
165
|
+
let firstDiffAt = -1;
|
|
166
|
+
for (let i = 0; i < Math.min(sa.length, sb.length); i++) {
|
|
167
|
+
if (sa[i] !== sb[i]) { firstDiffAt = i; break; }
|
|
168
|
+
}
|
|
169
|
+
out.push(
|
|
170
|
+
`${k}: serial len=${lenA}, parallel len=${lenB}, first diff at char ${firstDiffAt}\n` +
|
|
171
|
+
` serial: …${sa.slice(Math.max(0, firstDiffAt - 60), firstDiffAt + 100)}…\n` +
|
|
172
|
+
` parallel: …${sb.slice(Math.max(0, firstDiffAt - 60), firstDiffAt + 100)}…`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── Indexing helper ─────────────────────────────────────────────────────────
|
|
180
|
+
|
|
181
|
+
async function indexInto(
|
|
182
|
+
dbPath: string, root: string, parallel: boolean, jobs?: number,
|
|
183
|
+
): Promise<ReturnType<Indexer['indexDirectory']> extends Promise<infer R> ? R : never> {
|
|
184
|
+
const store = new Store(dbPath);
|
|
185
|
+
const indexer = new Indexer(store);
|
|
186
|
+
const result = await indexer.indexDirectory(root, {
|
|
187
|
+
quiet: true,
|
|
188
|
+
parallel,
|
|
189
|
+
jobs,
|
|
190
|
+
});
|
|
191
|
+
store.close();
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function snapshotDb(dbPath: string): DbSnapshot {
|
|
196
|
+
const store = new Store(dbPath);
|
|
197
|
+
const snap = dumpDb(store);
|
|
198
|
+
store.close();
|
|
199
|
+
return snap;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Run ─────────────────────────────────────────────────────────────────────
|
|
203
|
+
|
|
204
|
+
async function run(): Promise<void> {
|
|
205
|
+
console.log('\nParallel Indexing Parity Test (Step 4)');
|
|
206
|
+
console.log('========================================\n');
|
|
207
|
+
|
|
208
|
+
const root = stageFixtures();
|
|
209
|
+
console.log(` fixtures staged at ${root}\n`);
|
|
210
|
+
|
|
211
|
+
// ── 1. Build serial reference DB ──────────────────────────────────────────
|
|
212
|
+
const serialDb = path.join(os.tmpdir(), `seer-parallel-serial-${Date.now()}.db`);
|
|
213
|
+
const serialRes = await indexInto(serialDb, root, false);
|
|
214
|
+
console.log(`── Serial reference: ${serialRes.filesIndexed} indexed, ${serialRes.symbols} symbols, ${serialRes.edges} edges ──`);
|
|
215
|
+
const serialSnap = snapshotDb(serialDb);
|
|
216
|
+
assert(serialRes.filesIndexed > 0, 'serial run indexed at least one file');
|
|
217
|
+
|
|
218
|
+
// ── 2. Parallel DBs at multiple job counts must match serial exactly ─────
|
|
219
|
+
for (const jobs of [1, 2, 4, 8]) {
|
|
220
|
+
console.log(`\n── jobs=${jobs} parity ──`);
|
|
221
|
+
const dbPath = path.join(os.tmpdir(), `seer-parallel-${jobs}-${Date.now()}.db`);
|
|
222
|
+
const res = await indexInto(dbPath, root, true, jobs);
|
|
223
|
+
const snap = snapshotDb(dbPath);
|
|
224
|
+
|
|
225
|
+
assert(res.filesIndexed === serialRes.filesIndexed, `filesIndexed matches serial (${res.filesIndexed})`);
|
|
226
|
+
assert(res.symbols === serialRes.symbols, `symbols matches serial (${res.symbols})`);
|
|
227
|
+
assert(res.edges === serialRes.edges, `edges matches serial (${res.edges})`);
|
|
228
|
+
assert(res.resolvedEdges === serialRes.resolvedEdges, `resolvedEdges matches serial (${res.resolvedEdges})`);
|
|
229
|
+
|
|
230
|
+
const diffs = diffSnapshots(serialSnap, snap);
|
|
231
|
+
if (diffs.length > 0) {
|
|
232
|
+
for (const d of diffs) console.error(` diff: ${d}`);
|
|
233
|
+
}
|
|
234
|
+
assert(diffs.length === 0, `every DB table row-identical to serial (jobs=${jobs})`);
|
|
235
|
+
|
|
236
|
+
fs.unlinkSync(dbPath);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ── 3. Cache-hit re-index: second pass is a no-op ─────────────────────────
|
|
240
|
+
console.log(`\n── Cache-hit re-index (parallel) ──`);
|
|
241
|
+
const cacheDb = path.join(os.tmpdir(), `seer-parallel-cache-${Date.now()}.db`);
|
|
242
|
+
const firstPass = await indexInto(cacheDb, root, true, 4);
|
|
243
|
+
const firstSnap = snapshotDb(cacheDb);
|
|
244
|
+
assert(firstPass.filesIndexed > 0, 'first parallel pass indexed files');
|
|
245
|
+
assert(firstPass.pagerankRecomputed === true, 'first pass recomputes PageRank');
|
|
246
|
+
|
|
247
|
+
const secondPass = await indexInto(cacheDb, root, true, 4);
|
|
248
|
+
const secondSnap = snapshotDb(cacheDb);
|
|
249
|
+
assert(secondPass.filesIndexed === 0, `second pass indexed=0 (got ${secondPass.filesIndexed})`);
|
|
250
|
+
assert(secondPass.filesReusedFromCache === firstPass.filesIndexed,
|
|
251
|
+
`second pass reusedFromCache=${secondPass.filesReusedFromCache} matches first pass indexed=${firstPass.filesIndexed}`);
|
|
252
|
+
assert(secondPass.pagerankRecomputed === false, 'second pass skips PageRank (graph unchanged)');
|
|
253
|
+
const cacheDiffs = diffSnapshots(firstSnap, secondSnap);
|
|
254
|
+
if (cacheDiffs.length > 0) {
|
|
255
|
+
for (const d of cacheDiffs) console.error(` cache-diff: ${d}`);
|
|
256
|
+
}
|
|
257
|
+
assert(cacheDiffs.length === 0, 'cache-hit pass DB identical to first pass (touchedFileIds includes cached files → no pruning)');
|
|
258
|
+
|
|
259
|
+
// ── 4. Stale-file pruning: delete a file, re-index, row should vanish ────
|
|
260
|
+
console.log(`\n── Stale-file pruning (parallel) ──`);
|
|
261
|
+
const beforePrune = secondSnap; // already captured
|
|
262
|
+
const victim = 'trackcd_complex_module.py';
|
|
263
|
+
fs.unlinkSync(path.join(root, victim));
|
|
264
|
+
const prunePass = await indexInto(cacheDb, root, true, 4);
|
|
265
|
+
const afterPrune = snapshotDb(cacheDb);
|
|
266
|
+
const filesNow = (afterPrune.files as Array<{ rel_path: string }>).map(r => r.rel_path);
|
|
267
|
+
assert(!filesNow.includes(victim), `deleted file ${victim} pruned from DB`);
|
|
268
|
+
assert(
|
|
269
|
+
(afterPrune.files as unknown[]).length === (beforePrune.files as unknown[]).length - 1,
|
|
270
|
+
`files count dropped by 1 (was ${(beforePrune.files as unknown[]).length}, now ${(afterPrune.files as unknown[]).length})`,
|
|
271
|
+
);
|
|
272
|
+
// The victim's symbols / config_keys must cascade-delete.
|
|
273
|
+
const victimSymsBefore = (beforePrune.symbols as Array<{ file_id: number }>)
|
|
274
|
+
.filter(s => {
|
|
275
|
+
const fid = (beforePrune.files as Array<{ id: number; rel_path: string }>)
|
|
276
|
+
.find(f => f.rel_path === victim)?.id;
|
|
277
|
+
return fid != null && s.file_id === fid;
|
|
278
|
+
}).length;
|
|
279
|
+
assert(victimSymsBefore > 0, `victim had >0 symbols before delete (sanity check, got ${victimSymsBefore})`);
|
|
280
|
+
const victimFidNow = (afterPrune.files as Array<{ id: number; rel_path: string }>)
|
|
281
|
+
.find(f => f.rel_path === victim)?.id;
|
|
282
|
+
assert(victimFidNow === undefined, 'victim file row gone from files table');
|
|
283
|
+
assert(prunePass.pagerankRecomputed === true, 'prune triggers PageRank recompute');
|
|
284
|
+
|
|
285
|
+
// ── 5. One-file edit: only edited file's symbols change ──────────────────
|
|
286
|
+
console.log(`\n── One-file edit (parallel) ──`);
|
|
287
|
+
// Restage so prior-test mutations don't carry over.
|
|
288
|
+
const editRoot = stageFixtures();
|
|
289
|
+
const editDb = path.join(os.tmpdir(), `seer-parallel-edit-${Date.now()}.db`);
|
|
290
|
+
await indexInto(editDb, editRoot, true, 4);
|
|
291
|
+
const beforeEdit = snapshotDb(editDb);
|
|
292
|
+
|
|
293
|
+
// Append a new function to caller.ts so its hash changes.
|
|
294
|
+
const callerPath = path.join(editRoot, 'caller.ts');
|
|
295
|
+
fs.appendFileSync(callerPath, '\nexport function freshlyAdded(): number { return 42; }\n');
|
|
296
|
+
|
|
297
|
+
const editRes = await indexInto(editDb, editRoot, true, 4);
|
|
298
|
+
const afterEdit = snapshotDb(editDb);
|
|
299
|
+
assert(editRes.filesIndexed === 1, `exactly 1 file reindexed (got ${editRes.filesIndexed})`);
|
|
300
|
+
assert(editRes.filesReusedFromCache === beforeEdit.files.length - 1,
|
|
301
|
+
`everything else cache-reused (got ${editRes.filesReusedFromCache} of ${beforeEdit.files.length - 1})`);
|
|
302
|
+
|
|
303
|
+
const addedSym = (afterEdit.symbols as Array<{ name: string }>).find(s => s.name === 'freshlyAdded');
|
|
304
|
+
assert(addedSym !== undefined, 'freshlyAdded symbol present after edit');
|
|
305
|
+
|
|
306
|
+
const callerFidBefore = (beforeEdit.files as Array<{ id: number; rel_path: string }>)
|
|
307
|
+
.find(f => f.rel_path === 'caller.ts')?.id;
|
|
308
|
+
const callerFidAfter = (afterEdit.files as Array<{ id: number; rel_path: string }>)
|
|
309
|
+
.find(f => f.rel_path === 'caller.ts')?.id;
|
|
310
|
+
assert(callerFidBefore === callerFidAfter, 'caller.ts file id stable across edit');
|
|
311
|
+
assert(editRes.pagerankRecomputed === true, 'edit triggers PageRank recompute');
|
|
312
|
+
|
|
313
|
+
// ── Cleanup ──────────────────────────────────────────────────────────────
|
|
314
|
+
try { fs.unlinkSync(serialDb); } catch { /* */ }
|
|
315
|
+
try { fs.unlinkSync(cacheDb); } catch { /* */ }
|
|
316
|
+
try { fs.unlinkSync(editDb); } catch { /* */ }
|
|
317
|
+
try { fs.rmSync(root, { recursive: true, force: true }); } catch { /* */ }
|
|
318
|
+
try { fs.rmSync(editRoot, { recursive: true, force: true }); } catch { /* */ }
|
|
319
|
+
|
|
320
|
+
console.log('\n══════════════════════════════════════════════════════════════');
|
|
321
|
+
console.log(` Parallel-index results: ${passed} passed, ${failed} failed`);
|
|
322
|
+
if (failed > 0) {
|
|
323
|
+
console.error('\n PARALLEL-INDEX TESTS FAILED\n');
|
|
324
|
+
process.exit(1);
|
|
325
|
+
} else {
|
|
326
|
+
console.log('\n All parallel-index tests passed! ✓\n');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
run().catch(err => {
|
|
331
|
+
console.error('parallel-index test threw:', err);
|
|
332
|
+
process.exit(1);
|
|
333
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Track A regression net: parallel read-only opens against a busy DB.
|
|
3
|
+
*
|
|
4
|
+
* The dogfood gap was that `Store` always ran SCHEMA_SQL + migrations on
|
|
5
|
+
* open, which took a write lock — and on Windows that lock would conflict
|
|
6
|
+
* with a concurrent indexer's transaction, surfacing as `database is locked`
|
|
7
|
+
* for everyone. The fix is:
|
|
8
|
+
* - Store.openReadOnly() opens with readOnly: true, skips schema setup,
|
|
9
|
+
* sets PRAGMA query_only=ON + busy_timeout.
|
|
10
|
+
* - The writer also sets busy_timeout so brief reader contention waits
|
|
11
|
+
* instead of failing.
|
|
12
|
+
*
|
|
13
|
+
* This test pounds the read path while the writer indexes, then confirms:
|
|
14
|
+
* 1. No `SQLITE_BUSY` errors during 100 parallel read opens.
|
|
15
|
+
* 2. The reader sees a consistent snapshot — symbol count never decreases.
|
|
16
|
+
* 3. Writes through a read-only Store are rejected with a clear error.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import path from 'path';
|
|
20
|
+
import fs from 'fs';
|
|
21
|
+
import os from 'os';
|
|
22
|
+
import { Store } from '../src/db/store';
|
|
23
|
+
import { Indexer } from '../src/indexer/index';
|
|
24
|
+
import { CURRENT_SCHEMA_VERSION } from '../src/db/schema';
|
|
25
|
+
|
|
26
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
27
|
+
const FIXTURES = path.join(ROOT, 'tests/fixtures');
|
|
28
|
+
const TMP_DB = path.join(os.tmpdir(), `seer-parallel-${Date.now()}.db`);
|
|
29
|
+
|
|
30
|
+
let passed = 0;
|
|
31
|
+
let failed = 0;
|
|
32
|
+
function ok(label: string): void { passed++; console.log(` ✓ ${label}`); }
|
|
33
|
+
function bad(label: string, extra?: unknown): void {
|
|
34
|
+
failed++;
|
|
35
|
+
console.error(` ✗ ${label}` + (extra !== undefined ? ` :: ${JSON.stringify(extra)}` : ''));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function main(): Promise<void> {
|
|
39
|
+
console.log('\nSeer Parallel-Read Test\n=========================\n');
|
|
40
|
+
|
|
41
|
+
// Seed the DB with an initial index.
|
|
42
|
+
const writer = new Store(TMP_DB);
|
|
43
|
+
await new Indexer(writer).indexDirectory(FIXTURES, { quiet: true });
|
|
44
|
+
|
|
45
|
+
// Verify schema version stored properly — pinned at the current build.
|
|
46
|
+
const sinfo = writer.schemaInfo();
|
|
47
|
+
if (sinfo.current && sinfo.dbVersion === CURRENT_SCHEMA_VERSION) ok(`schema_version pinned at ${sinfo.dbVersion}`);
|
|
48
|
+
else bad('schema_version not pinned', sinfo);
|
|
49
|
+
|
|
50
|
+
writer.close();
|
|
51
|
+
|
|
52
|
+
// 100 parallel read-only opens, each doing a couple of cheap queries.
|
|
53
|
+
// No SQLITE_BUSY allowed.
|
|
54
|
+
const N = 100;
|
|
55
|
+
const errors: Error[] = [];
|
|
56
|
+
const counts: number[] = [];
|
|
57
|
+
await Promise.all(Array.from({ length: N }, async () => {
|
|
58
|
+
try {
|
|
59
|
+
const s = Store.openReadOnly(TMP_DB);
|
|
60
|
+
counts.push(s.getStats().symbols);
|
|
61
|
+
void s.findCallers('process_payment', 5);
|
|
62
|
+
void s.findSymbols('AuthService', { limit: 10 });
|
|
63
|
+
void s.getTopSymbols(5);
|
|
64
|
+
s.close();
|
|
65
|
+
} catch (e) {
|
|
66
|
+
errors.push(e as Error);
|
|
67
|
+
}
|
|
68
|
+
}));
|
|
69
|
+
if (errors.length === 0) ok(`${N} parallel read-only opens completed without errors`);
|
|
70
|
+
else bad(`${errors.length}/${N} parallel reads failed`, errors[0]?.message);
|
|
71
|
+
|
|
72
|
+
if (new Set(counts).size === 1) ok(`all ${N} reads saw the same symbol count (${counts[0]})`);
|
|
73
|
+
else bad('symbol count drifted between parallel reads', new Set(counts));
|
|
74
|
+
|
|
75
|
+
// Concurrent reader + writer. A reindex pass should not lock out readers.
|
|
76
|
+
const w2 = new Store(TMP_DB);
|
|
77
|
+
const indexer = new Indexer(w2);
|
|
78
|
+
|
|
79
|
+
let raceErrors = 0;
|
|
80
|
+
const writerDone = indexer.indexDirectory(FIXTURES, { quiet: true });
|
|
81
|
+
// Hammer reads while the indexer runs.
|
|
82
|
+
for (let i = 0; i < 25; i++) {
|
|
83
|
+
try {
|
|
84
|
+
const r = Store.openReadOnly(TMP_DB);
|
|
85
|
+
r.getStats();
|
|
86
|
+
r.close();
|
|
87
|
+
} catch {
|
|
88
|
+
raceErrors++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
await writerDone;
|
|
92
|
+
w2.close();
|
|
93
|
+
if (raceErrors === 0) ok('25 reads while writer was active: no SQLITE_BUSY');
|
|
94
|
+
else bad(`${raceErrors} reads failed while writer was active`);
|
|
95
|
+
|
|
96
|
+
// Read-only Store must reject writes.
|
|
97
|
+
const ro = Store.openReadOnly(TMP_DB);
|
|
98
|
+
let writeBlocked = false;
|
|
99
|
+
try {
|
|
100
|
+
ro.upsertFile('/tmp/bogus.ts', 'bogus.ts', 'typescript', 'abc', 0);
|
|
101
|
+
} catch (e) {
|
|
102
|
+
writeBlocked = true;
|
|
103
|
+
// The exact message varies (sqlite says "attempt to write a readonly database"
|
|
104
|
+
// or "no such column"); we only require that it threw.
|
|
105
|
+
process.stdout.write(` (read-only write rejected: ${(e as Error).message.substring(0, 60)})\n`);
|
|
106
|
+
}
|
|
107
|
+
if (writeBlocked) ok('read-only Store rejects writes');
|
|
108
|
+
else bad('read-only Store allowed a write');
|
|
109
|
+
ro.close();
|
|
110
|
+
|
|
111
|
+
// Cleanup.
|
|
112
|
+
for (const ext of ['', '-wal', '-shm']) {
|
|
113
|
+
const p = TMP_DB + ext;
|
|
114
|
+
if (fs.existsSync(p)) fs.unlinkSync(p);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(`\n══════════════════════════════════════════════════════════════`);
|
|
118
|
+
console.log(` Parallel-read results: ${passed} passed, ${failed} failed\n`);
|
|
119
|
+
if (failed > 0) process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
main().catch(err => {
|
|
123
|
+
console.error('Parallel-read test crashed:', err);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
});
|