pi-t3-pstack 0.0.0-stage → 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/LICENSE +23 -0
- package/index.ts +87 -0
- package/package.json +62 -4
- package/pstack/.claude-plugin/icon.png +0 -0
- package/pstack/.claude-plugin/plugin.json +35 -0
- package/pstack/.codex-plugin/plugin.json +50 -0
- package/pstack/README.md +26 -0
- package/pstack/agents/comment-sicko.md +32 -0
- package/pstack/agents/poteto-agent.md +8 -0
- package/pstack/agents/thermo-nuclear-code-quality-review-subagent.md +23 -0
- package/pstack/agents/thermo-nuclear-review-subagent.md +28 -0
- package/pstack/assets/pstack-icon.png +0 -0
- package/pstack/assets/pstack-icon.prompt.txt +8 -0
- package/pstack/hooks/codex-hooks.json +16 -0
- package/pstack/hooks/hooks.json +15 -0
- package/pstack/hooks/session-start-context.md +17 -0
- package/pstack/hooks/session-start.ps1 +33 -0
- package/pstack/hooks/session-start.sh +34 -0
- package/pstack/models.json +261 -0
- package/pstack/skills/architect/SKILL.md +85 -0
- package/pstack/skills/architect/references/design-red-flags.md +57 -0
- package/pstack/skills/architect/references/rationale-template.md +35 -0
- package/pstack/skills/architect/references/runner-prompt.md +20 -0
- package/pstack/skills/arena/SKILL.md +55 -0
- package/pstack/skills/automate-me/SKILL.md +104 -0
- package/pstack/skills/benchmark-checklist/SKILL.md +39 -0
- package/pstack/skills/blast-radius/SKILL.md +50 -0
- package/pstack/skills/bro/SKILL.md +7 -0
- package/pstack/skills/correct/SKILL.md +32 -0
- package/pstack/skills/create-formal-verification-skill/SKILL.md +43 -0
- package/pstack/skills/create-verification-skill/SKILL.md +44 -0
- package/pstack/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/pstack/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/pstack/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/pstack/skills/deslop/SKILL.md +22 -0
- package/pstack/skills/figure-it-out/SKILL.md +52 -0
- package/pstack/skills/fix-ci/SKILL.md +29 -0
- package/pstack/skills/fix-merge-conflicts/SKILL.md +32 -0
- package/pstack/skills/get-pr-comments/SKILL.md +23 -0
- package/pstack/skills/how/SKILL.md +43 -0
- package/pstack/skills/how/references/explainer-prompt.md +55 -0
- package/pstack/skills/how/references/explorer-prompt.md +35 -0
- package/pstack/skills/in-your-own-words/SKILL.md +7 -0
- package/pstack/skills/interrogate/SKILL.md +37 -0
- package/pstack/skills/ketchup/SKILL.md +43 -0
- package/pstack/skills/ketchup-video/SKILL.md +29 -0
- package/pstack/skills/maintain-formal-verification-skill/SKILL.md +58 -0
- package/pstack/skills/maintain-verification-skill/SKILL.md +39 -0
- package/pstack/skills/make-pr-easy-to-review/SKILL.md +59 -0
- package/pstack/skills/no-comments/SKILL.md +24 -0
- package/pstack/skills/poteto-help/SKILL.md +162 -0
- package/pstack/skills/poteto-help/references/prompting.md +51 -0
- package/pstack/skills/poteto-help/references/recipes.md +51 -0
- package/pstack/skills/poteto-mode/SKILL.md +186 -0
- package/pstack/skills/poteto-mode/brief.md +70 -0
- package/pstack/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/pstack/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/pstack/skills/poteto-mode/playbooks/autopilot-full.md +25 -0
- package/pstack/skills/poteto-mode/playbooks/autopilot-stack.md +8 -0
- package/pstack/skills/poteto-mode/playbooks/babysit.md +23 -0
- package/pstack/skills/poteto-mode/playbooks/bug-fix.md +15 -0
- package/pstack/skills/poteto-mode/playbooks/eval.md +25 -0
- package/pstack/skills/poteto-mode/playbooks/feature.md +21 -0
- package/pstack/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/pstack/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/pstack/skills/poteto-mode/playbooks/multi-phase-plan.md +155 -0
- package/pstack/skills/poteto-mode/playbooks/opening-a-pr.md +34 -0
- package/pstack/skills/poteto-mode/playbooks/orchestrate.md +25 -0
- package/pstack/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/pstack/skills/poteto-mode/playbooks/perf-issue.md +25 -0
- package/pstack/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/pstack/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/pstack/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/pstack/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/pstack/skills/poteto-mode/playbooks/shipping.md +12 -0
- package/pstack/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/pstack/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/pstack/skills/poteto-mode/playbooks/worktree-cleanup.md +12 -0
- package/pstack/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/pstack/skills/poteto-mode/references/licenses/LICENSE +23 -0
- package/pstack/skills/poteto-mode/references/licenses/LICENSE-cursor-team-kit +21 -0
- package/pstack/skills/poteto-mode/references/licenses/LICENSE-thermos +21 -0
- package/pstack/skills/poteto-mode/references/licenses/NOTICE.md +14 -0
- package/pstack/skills/poteto-mode/scripts/.prettierrc.json +3 -0
- package/pstack/skills/poteto-mode/scripts/bootstrap.ts +62 -0
- package/pstack/skills/poteto-mode/scripts/bun.lock +67 -0
- package/pstack/skills/poteto-mode/scripts/check-plan.mjs +205 -0
- package/pstack/skills/poteto-mode/scripts/check-plan.test.ts +110 -0
- package/pstack/skills/poteto-mode/scripts/package.json +12 -0
- package/pstack/skills/poteto-mode/scripts/resolve-codex-transcript +4 -0
- package/pstack/skills/poteto-mode/scripts/resolve-codex-transcript.ts +314 -0
- package/pstack/skills/poteto-mode/scripts/test-utils/node-test.ts +204 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/cli.test.ts +225 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/cli.ts +231 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +142 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/github.test.ts +498 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/github.ts +760 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/policy.test.ts +483 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/policy.ts +1023 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/render.ts +188 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/settle.test.ts +552 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/settle.ts +406 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/tsconfig.json +18 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/types.ts +444 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/verdicts.test.ts +796 -0
- package/pstack/skills/poteto-mode/scripts/watch-pr/watch-pr +3 -0
- package/pstack/skills/poteto-mode/scripts/worktree-audit.sh +170 -0
- package/pstack/skills/principle-attack-the-premise/SKILL.md +21 -0
- package/pstack/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/pstack/skills/principle-build-the-lever/SKILL.md +23 -0
- package/pstack/skills/principle-encode-lessons-in-structure/SKILL.md +11 -0
- package/pstack/skills/principle-exhaust-the-design-space/SKILL.md +9 -0
- package/pstack/skills/principle-experience-first/SKILL.md +19 -0
- package/pstack/skills/principle-explain-the-number/SKILL.md +18 -0
- package/pstack/skills/principle-fix-root-causes/SKILL.md +21 -0
- package/pstack/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/pstack/skills/principle-guard-the-context-window/SKILL.md +12 -0
- package/pstack/skills/principle-laziness-protocol/SKILL.md +10 -0
- package/pstack/skills/principle-make-operations-idempotent/SKILL.md +22 -0
- package/pstack/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +14 -0
- package/pstack/skills/principle-minimize-reader-load/SKILL.md +18 -0
- package/pstack/skills/principle-model-the-domain/SKILL.md +24 -0
- package/pstack/skills/principle-never-block-on-the-human/SKILL.md +15 -0
- package/pstack/skills/principle-outcome-oriented-execution/SKILL.md +21 -0
- package/pstack/skills/principle-prove-it-works/SKILL.md +22 -0
- package/pstack/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/pstack/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/pstack/skills/principle-sequence-verifiable-units/SKILL.md +17 -0
- package/pstack/skills/principle-subtract-before-you-add/SKILL.md +18 -0
- package/pstack/skills/principle-test-behavior-not-implementation/SKILL.md +20 -0
- package/pstack/skills/principle-type-system-discipline/SKILL.md +13 -0
- package/pstack/skills/recall/SKILL.md +35 -0
- package/pstack/skills/reflect/SKILL.md +86 -0
- package/pstack/skills/reflect/references/divergent-reviewer.md +38 -0
- package/pstack/skills/reflect/references/judgment-reviewer.md +37 -0
- package/pstack/skills/reflect/references/synthesizer.md +56 -0
- package/pstack/skills/reflect/references/tooling-reviewer.md +48 -0
- package/pstack/skills/reflect/scripts/find-transcript.mjs +245 -0
- package/pstack/skills/setup-pstack/SKILL.md +77 -0
- package/pstack/skills/setup-pstack/scripts/read-sheet.sh +14 -0
- package/pstack/skills/setup-pstack/scripts/t3-sheet.mjs +169 -0
- package/pstack/skills/sharpen/SKILL.md +16 -0
- package/pstack/skills/show-me-your-work/SKILL.md +82 -0
- package/pstack/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/pstack/skills/show-me-your-work/scripts/log.sh +42 -0
- package/pstack/skills/statechart/SKILL.md +82 -0
- package/pstack/skills/statechart/references/render.md +93 -0
- package/pstack/skills/statechart/scripts/backends/cfg.ts +33 -0
- package/pstack/skills/statechart/scripts/backends/mermaid.ts +201 -0
- package/pstack/skills/statechart/scripts/backends/plain-text.ts +72 -0
- package/pstack/skills/statechart/scripts/backends/report.ts +177 -0
- package/pstack/skills/statechart/scripts/frontends/config.ts +276 -0
- package/pstack/skills/statechart/scripts/frontends/dot.ts +74 -0
- package/pstack/skills/statechart/scripts/frontends/marks.ts +93 -0
- package/pstack/skills/statechart/scripts/frontends/messages.ts +52 -0
- package/pstack/skills/statechart/scripts/frontends/spec.ts +114 -0
- package/pstack/skills/statechart/scripts/frontends/trace.ts +24 -0
- package/pstack/skills/statechart/scripts/frontends/value.ts +166 -0
- package/pstack/skills/statechart/scripts/frontends/verdict.ts +40 -0
- package/pstack/skills/statechart/scripts/frontends/view.ts +119 -0
- package/pstack/skills/statechart/scripts/ir.ts +547 -0
- package/pstack/skills/statechart/scripts/package.json +11 -0
- package/pstack/skills/statechart/scripts/passes/annotate.ts +128 -0
- package/pstack/skills/statechart/scripts/passes/label.ts +269 -0
- package/pstack/skills/statechart/scripts/passes/lift.ts +85 -0
- package/pstack/skills/statechart/scripts/passes/loop-check.ts +68 -0
- package/pstack/skills/statechart/scripts/passes/paths.ts +60 -0
- package/pstack/skills/statechart/scripts/passes/reach.ts +135 -0
- package/pstack/skills/statechart/scripts/passes/region-check.ts +97 -0
- package/pstack/skills/statechart/scripts/passes/regions.ts +295 -0
- package/pstack/skills/statechart/scripts/pipeline.ts +76 -0
- package/pstack/skills/statechart/scripts/run.ts +481 -0
- package/pstack/skills/statechart/scripts/statechart +29 -0
- package/pstack/skills/statechart/scripts/statechart.ts +164 -0
- package/pstack/skills/statechart/scripts/test/ConfigOracle.java +63 -0
- package/pstack/skills/statechart/scripts/test/chart.test.ts +1210 -0
- package/pstack/skills/statechart/scripts/test/charts/Braces.mmd +7 -0
- package/pstack/skills/statechart/scripts/test/charts/Escapes.mmd +11 -0
- package/pstack/skills/statechart/scripts/test/charts/Phases.mmd +29 -0
- package/pstack/skills/statechart/scripts/test/cli.test.ts +1314 -0
- package/pstack/skills/statechart/scripts/test/config-fuzz.test.ts +263 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/Counter.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/Counter.tla +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/chart.mmd +9 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/graph.dot +16 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/report.md +7 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/Counter/verdict-1.out +49 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/ChartRun.out +60 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/CounterAction.cfg +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/CounterAction.tla +22 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/chart.mmd +26 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/graph.dot +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/report.md +20 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-2.out +82 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterAction/verdict-3.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/CounterDeadlock.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/CounterDeadlock.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/chart.mmd +22 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/graph.dot +16 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/report.md +17 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterDeadlock/verdict-2.out +77 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/ChartRun.out +69 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/CounterFan.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/CounterFan.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/chart.mmd +85 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/graph.dot +70 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/report.md +12 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/verdict-1.out +67 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterFan/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/ChartRun.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/ChartRun.out +85 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/CounterGuard.cfg +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/CounterGuard.tla +13 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/chart.mmd +5 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/graph.dot +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/report.md +31 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-1.out +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterGuard/verdict-2.out +77 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/ChartRun.out +55 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/CounterInitPast.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/CounterInitPast.tla +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/chart.mmd +12 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/graph.dot +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/report.md +12 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-1.out +33 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPast/verdict-2.out +54 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/ChartRun.out +54 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/CounterInitPastHolds.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/CounterInitPastHolds.tla +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/chart.mmd +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/graph.dot +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/report.md +7 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInitPastHolds/verdict-1.out +46 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/CounterInstance.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/CounterInstance.tla +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/CounterSteps.tla +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/chart.mmd +17 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/graph.dot +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/report.md +14 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/sany.out +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterInstance/verdict-1.out +78 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/CounterJump.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/CounterJump.tla +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/chart.mmd +26 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/graph.dot +22 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/report.md +12 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/verdict-1.out +58 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterJump/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/CounterLoop.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/CounterLoop.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/chart.mmd +17 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/graph.dot +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/report.md +14 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterLoop/verdict-1.out +76 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/CounterOrder.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/CounterOrder.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/chart.mmd +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/graph.dot +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/report.md +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterOrder/verdict-2.out +67 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/ChartRun.out +62 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/CounterRules.cfg +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/CounterRules.tla +24 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/Moves.tla +5 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/chart.mmd +22 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/graph.dot +10 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/report.md +23 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/sany.out +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-1.out +58 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-2.out +66 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-3.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-3.out +74 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterRules/verdict-4.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/ChartRun.out +58 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/CounterStart.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/CounterStart.tla +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/chart.mmd +18 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/graph.dot +13 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/report.md +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/verdict-1.out +33 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStart/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/CounterStop.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/CounterStop.tla +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/chart.mmd +7 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/graph.dot +13 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/report.md +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterStop/verdict-1.out +49 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/ChartRun.out +61 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/CounterTie.cfg +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/CounterTie.tla +14 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/chart.mmd +30 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/graph.dot +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/report.md +27 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-2.out +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-3.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-3.out +87 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTie/verdict-4.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/ChartRun.out +60 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/CounterTieInGraph.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/CounterTieInGraph.tla +12 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/chart.mmd +23 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/graph.dot +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/report.md +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterTieInGraph/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/ChartRun.out +61 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/CounterView.cfg +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/CounterView.tla +14 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/chart.mmd +23 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/graph.dot +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/report.md +27 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-2.out +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-3.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-3.out +87 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/verdict-4.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/CounterView/view.mjs +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/DieHard.cfg +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/DieHard.tla +38 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/chart.mmd +92 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/graph.dot +126 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/report.md +17 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/verdict-1.out +91 -0
- package/pstack/skills/statechart/scripts/test/corpus/DieHard/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/ChartRun.out +61 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/FirstRule.cfg +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/FirstRule.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/chart.mmd +22 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/graph.dot +16 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/report.md +25 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-2.out +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-3.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-3.out +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-4.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/FirstRule/verdict-4.out +77 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/HiddenClock.cfg +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/HiddenClock.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/chart.mmd +13 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/graph.dot +12 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/report.md +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/HiddenClock/verdict-1.out +72 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/Login.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/Login.tla +62 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/chart.mmd +27 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/graph.dot +40 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/report.md +7 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/verdict-1.out +47 -0
- package/pstack/skills/statechart/scripts/test/corpus/Login/view.mjs +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/LoginGuarded.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/LoginGuarded.tla +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/chart.mmd +28 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/graph.dot +37 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/report.md +8 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/verdict-1.out +47 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginGuarded/view.mjs +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/ChartRun.out +60 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/LoginParallel.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/LoginParallel.tla +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/chart.mmd +44 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/graph.dot +58 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/report.md +17 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/verdict-1.out +83 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/LoginParallel/view.mjs +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/ChartRun.out +60 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/MediaPlayer.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/MediaPlayer.tla +103 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/chart.mmd +43 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/graph.dot +93 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/report.md +14 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-1.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/verdict-2.out +72 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayer/view.mjs +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/MediaPlayerFixed.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/MediaPlayerFixed.tla +100 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/chart.mmd +31 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/graph.dot +85 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/report.md +9 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/verdict-1.out +47 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerFixed/view.mjs +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayer.tla +103 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayerProps.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/MediaPlayerProps.tla +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/chart.mmd +42 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/graph.dot +93 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/report.md +19 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-1.out +61 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-2.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-3.out +61 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-4.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-4.out +66 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-5.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-5.out +58 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/verdict-6.out +49 -0
- package/pstack/skills/statechart/scripts/test/corpus/MediaPlayerProps/view.mjs +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/PickWorker.cfg +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/PickWorker.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/chart.mmd +5 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/graph.dot +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/report.md +7 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/sany.out +5 -0
- package/pstack/skills/statechart/scripts/test/corpus/PickWorker/verdict-1.out +70 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/ChartRun.out +61 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/Shadow.cfg +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/Shadow.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/ShadowLib.tla +6 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/ShadowSub.tla +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/chart.mmd +9 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/graph.dot +16 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/report.md +8 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/sany.out +4 -0
- package/pstack/skills/statechart/scripts/test/corpus/Shadow/verdict-1.out +65 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/ChartRun.out +59 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/SpacedNames.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/SpacedNames.tla +9 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/chart.mmd +23 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/graph.dot +17 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/report.md +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-1.out +65 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/SpacedNames/verdict-2.out +75 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/ChartRun.out +69 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/TwoPhase.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/TwoPhase.tla +75 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/chart.mmd +60 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/graph.dot +297 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/report.md +20 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/verdict-1.out +95 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/verdict-2.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhase/view.mjs +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/ChartRun.out +57 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/TwoPhaseFixed.cfg +3 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/TwoPhaseFixed.tla +75 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/chart.mmd +37 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/graph.dot +223 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/report.md +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/sany.out +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/verdict-1.out +47 -0
- package/pstack/skills/statechart/scripts/test/corpus/TwoPhaseFixed/view.mjs +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/ChartRun.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/ChartRun.out +75 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/TypeGuard.cfg +5 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/TypeGuard.tla +11 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/chart.mmd +18 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/graph.dot +14 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/report.md +29 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-1.out +64 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/TypeGuard/verdict-2.out +75 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/ChartRun.out +58 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/UserNoDeadlock.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/UserNoDeadlock.tla +8 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/chart.mmd +22 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/graph.dot +16 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/report.md +15 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-1.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-1.out +56 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-2.exit +1 -0
- package/pstack/skills/statechart/scripts/test/corpus/UserNoDeadlock/verdict-2.out +77 -0
- package/pstack/skills/statechart/scripts/test/corpus.test.ts +403 -0
- package/pstack/skills/statechart/scripts/test/differential.test.ts +565 -0
- package/pstack/skills/statechart/scripts/test/grid/Grid.cfg +2 -0
- package/pstack/skills/statechart/scripts/test/grid/Grid.tla +9 -0
- package/pstack/skills/statechart/scripts/test/grid/sany.out +2 -0
- package/pstack/skills/statechart/scripts/test/java.ts +63 -0
- package/pstack/skills/statechart/scripts/test/labels.ts +643 -0
- package/pstack/skills/statechart/scripts/test/messages.test.ts +212 -0
- package/pstack/skills/statechart/scripts/test/pipeline.test.ts +270 -0
- package/pstack/skills/statechart/scripts/test/plain-tlc.test.ts +255 -0
- package/pstack/skills/statechart/scripts/test/render.test.ts +81 -0
- package/pstack/skills/statechart/scripts/test/tlc-out.test.ts +90 -0
- package/pstack/skills/statechart/scripts/test/tlc.ts +158 -0
- package/pstack/skills/statechart/scripts/tsconfig.json +11 -0
- package/pstack/skills/statechart/scripts/worker.ts +35 -0
- package/pstack/skills/swarm/SKILL.md +47 -0
- package/pstack/skills/tdd/SKILL.md +42 -0
- package/pstack/skills/teach/SKILL.md +21 -0
- package/pstack/skills/technical-writing/SKILL.md +106 -0
- package/pstack/skills/thermo-nuclear-code-quality-review/SKILL.md +192 -0
- package/pstack/skills/thermo-nuclear-review/SKILL.md +51 -0
- package/pstack/skills/thermos/SKILL.md +21 -0
- package/pstack/skills/typescript-best-practices/SKILL.md +31 -0
- package/pstack/skills/typescript-best-practices/references/patterns.md +328 -0
- package/pstack/skills/unslop/SKILL.md +62 -0
- package/pstack/skills/what-did-i-get-done/SKILL.md +31 -0
- package/pstack/skills/why/SKILL.md +105 -0
- package/pstack/skills/why/references/epistemics.md +71 -0
- package/pstack/skills/why/references/investigator-prompt.md +84 -0
- package/pstack/skills/why/references/source-playbook.md +17 -0
- package/pstack/skills/why/references/sources/code-archaeology.md +51 -0
- package/pstack/skills/why/references/sources/databricks.md +70 -0
- package/pstack/skills/why/references/sources/datadog.md +97 -0
- package/pstack/skills/why/references/sources/incident-postmortem.md +5 -0
- package/pstack/skills/why/references/sources/linear.md +48 -0
- package/pstack/skills/why/references/sources/notion.md +55 -0
- package/pstack/skills/why/references/sources/sentry.md +66 -0
- package/pstack/skills/why/references/sources/slack.md +17 -0
- package/pstack/skills/why/references/synthesizer-prompt.md +129 -0
- package/src/accounts.ts +527 -0
- package/src/bash-timeout.ts +63 -0
- package/src/bridge-trim.ts +109 -0
- package/src/brief.ts +280 -0
- package/src/cards.ts +65 -0
- package/src/child.ts +570 -0
- package/src/failures.ts +73 -0
- package/src/models.ts +1249 -0
- package/src/plan.ts +252 -0
- package/src/resources.ts +30 -0
- package/src/results.ts +92 -0
- package/src/resume.ts +143 -0
- package/src/session.ts +70 -0
- package/src/state.ts +124 -0
- package/src/t3-http.ts +140 -0
- package/src/t3-json.ts +50 -0
- package/src/t3-mcp.ts +188 -0
- package/src/tasks.ts +163 -0
- package/src/transcript.ts +17 -0
- package/src/usage.ts +145 -0
- package/README.md +0 -3
|
@@ -0,0 +1,1314 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
+
import { once } from "node:events";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { test } from "node:test";
|
|
8
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
10
|
+
import { plainText } from "../backends/plain-text.ts";
|
|
11
|
+
import { readMessages } from "../frontends/messages.ts";
|
|
12
|
+
|
|
13
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const corpus = path.join(here, "corpus");
|
|
15
|
+
const read = (file: string) => fs.readFileSync(file, "utf8");
|
|
16
|
+
|
|
17
|
+
function run(
|
|
18
|
+
dir: string,
|
|
19
|
+
{
|
|
20
|
+
args = [],
|
|
21
|
+
view = path.join(dir, "view.mjs"),
|
|
22
|
+
java = path.join(here, "java.ts"),
|
|
23
|
+
command = [process.execPath, path.join(here, "../statechart.ts")],
|
|
24
|
+
env = {},
|
|
25
|
+
}: {
|
|
26
|
+
args?: string[];
|
|
27
|
+
view?: string;
|
|
28
|
+
java?: string;
|
|
29
|
+
command?: string[];
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
} = {},
|
|
32
|
+
) {
|
|
33
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-cli-"));
|
|
34
|
+
const out = path.join(tmp, "out");
|
|
35
|
+
const log = path.join(tmp, "configs.log");
|
|
36
|
+
const name = path.basename(dir);
|
|
37
|
+
const [file, ...entry] = command;
|
|
38
|
+
const r = spawnSync(
|
|
39
|
+
file,
|
|
40
|
+
[
|
|
41
|
+
...entry,
|
|
42
|
+
path.join(dir, `${name}.tla`),
|
|
43
|
+
...(fs.existsSync(view) ? ["--view", view] : []),
|
|
44
|
+
"--out",
|
|
45
|
+
out,
|
|
46
|
+
...args,
|
|
47
|
+
],
|
|
48
|
+
{
|
|
49
|
+
encoding: "utf8",
|
|
50
|
+
env: {
|
|
51
|
+
...process.env,
|
|
52
|
+
JAVA: java,
|
|
53
|
+
TLA2TOOLS: "tla2tools.jar",
|
|
54
|
+
STATECHART_REPLAY: dir,
|
|
55
|
+
STATECHART_LOG: log,
|
|
56
|
+
...env,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
const configs: { [name: string]: string } = {};
|
|
61
|
+
if (fs.existsSync(log))
|
|
62
|
+
for (const c of read(log).split(/^## /m)) if (c) configs[c.slice(0, c.indexOf("\n"))] = c;
|
|
63
|
+
const files = fs.existsSync(out) ? fs.readdirSync(out).sort() : [];
|
|
64
|
+
return {
|
|
65
|
+
...r,
|
|
66
|
+
out,
|
|
67
|
+
files,
|
|
68
|
+
configs,
|
|
69
|
+
report: files.includes("report.md") ? read(path.join(out, "report.md")) : "",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function copyCase(name: string, edit: (dir: string) => void = () => {}) {
|
|
74
|
+
const dir = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-case-")), name);
|
|
75
|
+
fs.cpSync(path.join(corpus, name), dir, { recursive: true });
|
|
76
|
+
edit(dir);
|
|
77
|
+
return dir;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const named = (report: string) => {
|
|
81
|
+
const files: string[] = [];
|
|
82
|
+
for (const [file] of report.matchAll(/\btlc-\w+\.out\b/g))
|
|
83
|
+
if (!files.includes(file)) files.push(file);
|
|
84
|
+
return files;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
for (const name of fs.readdirSync(corpus)) {
|
|
88
|
+
test(`${name} writes the committed chart and report from TLC's output`, () => {
|
|
89
|
+
const dir = path.join(corpus, name);
|
|
90
|
+
const r = run(dir);
|
|
91
|
+
assert.equal(r.stderr, "");
|
|
92
|
+
const first = path.join(dir, "verdict-1.exit");
|
|
93
|
+
const plain = fs.existsSync(first) ? Number(read(first)) : 0;
|
|
94
|
+
assert.equal(r.status, plain === 0 ? 0 : [11, 12, 13].includes(plain) ? 1 : 2);
|
|
95
|
+
assert.equal(r.report, read(path.join(dir, "report.md")));
|
|
96
|
+
assert.equal(read(path.join(r.out, "chart.mmd")), read(path.join(dir, "chart.mmd")));
|
|
97
|
+
assert.deepEqual(r.files, ["chart.mmd", "report.md", "tlc.out", ...named(r.report)].sort());
|
|
98
|
+
for (const f of r.files) assert.doesNotMatch(read(path.join(r.out, f)), /@!@!@/, f);
|
|
99
|
+
assert.match(r.configs.ChartRun, /^INVARIANT ChartMark$/m);
|
|
100
|
+
assert.match(r.configs.ChartRun, /^CONSTRAINT ChartBudget$/m);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const launcher = path.join(here, "../statechart");
|
|
105
|
+
|
|
106
|
+
const launcherTools = () => {
|
|
107
|
+
const bin = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-bin-"));
|
|
108
|
+
for (const tool of ["dirname", "readlink", "sed"]) {
|
|
109
|
+
const found = spawnSync("sh", ["-c", `command -v ${tool}`], { encoding: "utf8" });
|
|
110
|
+
fs.symlinkSync(found.stdout.trim(), path.join(bin, tool));
|
|
111
|
+
}
|
|
112
|
+
return bin;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const linkedLauncher = () => {
|
|
116
|
+
const links = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-links-"));
|
|
117
|
+
const [first, second] = [path.join(links, "a"), path.join(links, "b")];
|
|
118
|
+
fs.mkdirSync(first);
|
|
119
|
+
fs.mkdirSync(second);
|
|
120
|
+
fs.symlinkSync(path.relative(second, launcher), path.join(second, "statechart"));
|
|
121
|
+
fs.symlinkSync(path.join(second, "statechart"), path.join(first, "statechart"));
|
|
122
|
+
return path.join(first, "statechart");
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
for (const [how, command] of [
|
|
126
|
+
["by its own path", () => launcher],
|
|
127
|
+
["through an absolute and a relative symlink", linkedLauncher],
|
|
128
|
+
] satisfies [string, () => string][]) {
|
|
129
|
+
test(`the launcher reached ${how} writes the committed chart on this Node`, () => {
|
|
130
|
+
const dir = path.join(corpus, "MediaPlayer");
|
|
131
|
+
const r = run(dir, {
|
|
132
|
+
command: [command()],
|
|
133
|
+
env: { PATH: `${path.dirname(process.execPath)}${path.delimiter}${launcherTools()}` },
|
|
134
|
+
});
|
|
135
|
+
assert.equal(r.stderr, "");
|
|
136
|
+
assert.equal(r.status, 1);
|
|
137
|
+
assert.equal(read(path.join(r.out, "chart.mmd")), read(path.join(dir, "chart.mmd")));
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const unsetNode = () => {
|
|
142
|
+
const bin = launcherTools();
|
|
143
|
+
fs.writeFileSync(
|
|
144
|
+
path.join(bin, "node"),
|
|
145
|
+
'#!/bin/sh\necho "No version is set for node." >&2\nexit 1\n',
|
|
146
|
+
{ mode: 0o755 },
|
|
147
|
+
);
|
|
148
|
+
return bin;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const fakeBun = (version: string) => () => {
|
|
152
|
+
const bin = launcherTools();
|
|
153
|
+
fs.writeFileSync(
|
|
154
|
+
path.join(bin, "bun"),
|
|
155
|
+
`#!/bin/sh\n[ "$1" = --version ] && echo ${version} && exit\necho "$1"\n`,
|
|
156
|
+
{ mode: 0o755 },
|
|
157
|
+
);
|
|
158
|
+
return bin;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
test("the launcher runs statechart.ts on Bun 1.1.5 when PATH has no Node", () => {
|
|
162
|
+
const r = run(path.join(corpus, "Login"), {
|
|
163
|
+
command: [launcher],
|
|
164
|
+
env: { PATH: fakeBun("1.1.5")() },
|
|
165
|
+
});
|
|
166
|
+
assert.equal(r.stderr, "");
|
|
167
|
+
assert.equal(r.status, 0);
|
|
168
|
+
assert.equal(r.stdout, `${path.join(here, "../statechart.ts")}\n`);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
for (const [what, bin] of [
|
|
172
|
+
["has none", launcherTools],
|
|
173
|
+
["has only a node that fails --version", unsetNode],
|
|
174
|
+
["has only Bun 1.1.4", fakeBun("1.1.4")],
|
|
175
|
+
] satisfies [string, () => string][]) {
|
|
176
|
+
test(`the launcher stops with exit 127 and names only the runtimes when PATH ${what}`, () => {
|
|
177
|
+
const r = run(path.join(corpus, "Login"), { command: [launcher], env: { PATH: bin() } });
|
|
178
|
+
assert.equal(r.status, 127);
|
|
179
|
+
assert.equal(
|
|
180
|
+
r.stderr,
|
|
181
|
+
"statechart needs Node 22.18 to 22.x, Node 23.6 or later, Bun 1.1.5 or later, or Deno 2.0 or later.\n",
|
|
182
|
+
);
|
|
183
|
+
assert.deepEqual(r.files, []);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
test("the order of TLC's dump and its state IDs do not change the chart or the report", () => {
|
|
188
|
+
let seed = 7;
|
|
189
|
+
const random = () => (seed = (seed * 48271) % 2147483647) / 2147483647;
|
|
190
|
+
for (const name of ["DieHard", "LoginParallel", "TwoPhase"]) {
|
|
191
|
+
const dir = copyCase(name, (d) => {
|
|
192
|
+
const file = path.join(d, "graph.dot");
|
|
193
|
+
const ids = new Map<string, string>();
|
|
194
|
+
const id = (n: string) => ids.get(n) ?? ids.set(n, String(9000 - ids.size * 17)).get(n);
|
|
195
|
+
const lines: string[] = [];
|
|
196
|
+
for (const l of read(file).split("\n"))
|
|
197
|
+
if (/^-?\d+ (\[|->)/.test(l))
|
|
198
|
+
lines.push(
|
|
199
|
+
l.replace(
|
|
200
|
+
/^(-?\d+)(?: -> (-?\d+))?/,
|
|
201
|
+
(_: string, a: string, b: string | undefined) => id(a) + (b ? ` -> ${id(b)}` : ""),
|
|
202
|
+
),
|
|
203
|
+
);
|
|
204
|
+
for (let i = lines.length - 1; i > 0; i--) {
|
|
205
|
+
const j = Math.floor(random() * (i + 1));
|
|
206
|
+
[lines[i], lines[j]] = [lines[j], lines[i]];
|
|
207
|
+
}
|
|
208
|
+
fs.writeFileSync(file, ["strict digraph DiskGraph {", ...lines, "}"].join("\n"));
|
|
209
|
+
});
|
|
210
|
+
const r = run(dir);
|
|
211
|
+
assert.equal(r.report, read(path.join(corpus, name, "report.md")), name);
|
|
212
|
+
assert.equal(
|
|
213
|
+
read(path.join(r.out, "chart.mmd")),
|
|
214
|
+
read(path.join(corpus, name, "chart.mmd")),
|
|
215
|
+
name,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
function startCommand(
|
|
221
|
+
dir: string,
|
|
222
|
+
parent: string,
|
|
223
|
+
{
|
|
224
|
+
java = path.join(here, "java.ts"),
|
|
225
|
+
tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-tmpdir-")),
|
|
226
|
+
}: { java?: string; tmpdir?: string } = {},
|
|
227
|
+
) {
|
|
228
|
+
const name = path.basename(dir);
|
|
229
|
+
const view = path.join(dir, "view.mjs");
|
|
230
|
+
return spawn(
|
|
231
|
+
process.execPath,
|
|
232
|
+
[
|
|
233
|
+
path.join(here, "../statechart.ts"),
|
|
234
|
+
path.join(dir, `${name}.tla`),
|
|
235
|
+
...(fs.existsSync(view) ? ["--view", view] : []),
|
|
236
|
+
"--out",
|
|
237
|
+
path.join(parent, "out"),
|
|
238
|
+
],
|
|
239
|
+
{
|
|
240
|
+
env: {
|
|
241
|
+
...process.env,
|
|
242
|
+
JAVA: java,
|
|
243
|
+
TLA2TOOLS: "tla2tools.jar",
|
|
244
|
+
STATECHART_REPLAY: dir,
|
|
245
|
+
TMPDIR: tmpdir,
|
|
246
|
+
},
|
|
247
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function signalWhile<T extends { dir: string; reached: (parent: string) => boolean }>(
|
|
253
|
+
signal: NodeJS.Signals,
|
|
254
|
+
setup: (fakes: string) => T & { java?: string },
|
|
255
|
+
) {
|
|
256
|
+
const fakes = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-fakes-"));
|
|
257
|
+
const parent = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-parent-"));
|
|
258
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-tmpdir-"));
|
|
259
|
+
const phase = setup(fakes);
|
|
260
|
+
const child = startCommand(phase.dir, parent, { java: phase.java, tmpdir });
|
|
261
|
+
let stderr = "";
|
|
262
|
+
child.stderr.on("data", (chunk: Buffer) => (stderr += chunk));
|
|
263
|
+
const closed = once(child, "close");
|
|
264
|
+
const end = Date.now() + 60_000;
|
|
265
|
+
while (!phase.reached(parent))
|
|
266
|
+
assert.ok(Date.now() < end, "The command did not reach the phase.");
|
|
267
|
+
child.kill(signal);
|
|
268
|
+
const [status] = await closed;
|
|
269
|
+
assert.equal(stderr, `${signal} stopped the check, so the command wrote nothing.\n`);
|
|
270
|
+
assert.equal(status, 128 + os.constants.signals[signal]);
|
|
271
|
+
assert.deepEqual(fs.readdirSync(parent), []);
|
|
272
|
+
assert.deepEqual(fs.readdirSync(tmpdir), []);
|
|
273
|
+
return phase;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function assertGone(pid: number) {
|
|
277
|
+
for (let i = 0; ; i++) {
|
|
278
|
+
let code: string | undefined;
|
|
279
|
+
try {
|
|
280
|
+
process.kill(pid, 0);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
code = (error as NodeJS.ErrnoException).code;
|
|
283
|
+
// macOS gives EPERM for a process group of only zombies, until they are reaped.
|
|
284
|
+
if (code !== "EPERM") {
|
|
285
|
+
assert.equal(code, "ESRCH");
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
assert.ok(
|
|
290
|
+
i < 250,
|
|
291
|
+
`Process ${pid} ${code ? "exited, but it was not reaped" : "is still running"}.`,
|
|
292
|
+
);
|
|
293
|
+
await sleep(20);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function assertEmpty(dir: string) {
|
|
298
|
+
for (let i = 0; i < 250 && fs.readdirSync(dir).length; i++) await sleep(20);
|
|
299
|
+
assert.deepEqual(fs.readdirSync(dir), []);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function gridCase(n: number) {
|
|
303
|
+
const dir = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-case-")), "Grid");
|
|
304
|
+
fs.cpSync(path.join(here, "grid"), dir, { recursive: true });
|
|
305
|
+
for (const run of ["ChartRun.out", "verdict-1.out"])
|
|
306
|
+
fs.copyFileSync(path.join(corpus, "Login", run), path.join(dir, run));
|
|
307
|
+
const id = (x: number, y: number) => String(1_000_000 + x * n + y);
|
|
308
|
+
const lines = ["strict digraph DiskGraph {"];
|
|
309
|
+
for (let x = 0; x < n; x++)
|
|
310
|
+
for (let y = 0; y < n; y++) {
|
|
311
|
+
lines.push(
|
|
312
|
+
`${id(x, y)} [label="/\\\\ x = ${x}\\n/\\\\ y = ${y}"${x + y ? "" : ",style = filled"}]`,
|
|
313
|
+
);
|
|
314
|
+
if (x + 1 < n) lines.push(`${id(x, y)} -> ${id(x + 1, y)} [label="IncX"];`);
|
|
315
|
+
if (y + 1 < n) lines.push(`${id(x, y)} -> ${id(x, y + 1)} [label="IncY"];`);
|
|
316
|
+
}
|
|
317
|
+
lines.push("}");
|
|
318
|
+
fs.writeFileSync(path.join(dir, "graph.dot"), lines.join("\n"));
|
|
319
|
+
return dir;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function sleepyJava(fakes: string) {
|
|
323
|
+
const java = path.join(fakes, "java.mjs");
|
|
324
|
+
const pidFile = path.join(fakes, "java.pid");
|
|
325
|
+
fs.writeFileSync(
|
|
326
|
+
java,
|
|
327
|
+
`#!/usr/bin/env node\nimport fs from "node:fs";\n` +
|
|
328
|
+
`process.on("SIGTERM", () => {});\n` +
|
|
329
|
+
`fs.writeFileSync(${JSON.stringify(pidFile)}, String(process.pid));\n` +
|
|
330
|
+
`setTimeout(() => {}, 60_000);\n`,
|
|
331
|
+
{ mode: 0o755 },
|
|
332
|
+
);
|
|
333
|
+
return { java, pidFile };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
test(
|
|
337
|
+
"a SIGKILL of the command stops TLC, removes the temporary folder, and writes nothing",
|
|
338
|
+
{ timeout: 30_000 },
|
|
339
|
+
async () => {
|
|
340
|
+
const fakes = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-fakes-"));
|
|
341
|
+
const parent = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-parent-"));
|
|
342
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-tmpdir-"));
|
|
343
|
+
const { java, pidFile } = sleepyJava(fakes);
|
|
344
|
+
const child = startCommand(path.join(corpus, "DieHard"), parent, { java, tmpdir });
|
|
345
|
+
const exited = once(child, "exit");
|
|
346
|
+
while (!fs.existsSync(pidFile)) await sleep(20);
|
|
347
|
+
child.kill("SIGKILL");
|
|
348
|
+
await exited;
|
|
349
|
+
await assertGone(Number(read(pidFile)));
|
|
350
|
+
await assertEmpty(parent);
|
|
351
|
+
await assertEmpty(tmpdir);
|
|
352
|
+
},
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
test(
|
|
356
|
+
"a SIGKILL of the command while the view draws the chart leaves no folder behind",
|
|
357
|
+
{ timeout: 30_000 },
|
|
358
|
+
async () => {
|
|
359
|
+
const fakes = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-fakes-"));
|
|
360
|
+
const parent = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-parent-"));
|
|
361
|
+
const tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-tmpdir-"));
|
|
362
|
+
const drawing = path.join(fakes, "drawing");
|
|
363
|
+
const release = path.join(fakes, "release");
|
|
364
|
+
const dir = copyCase("Login", (d) =>
|
|
365
|
+
fs.writeFileSync(
|
|
366
|
+
path.join(d, "view.mjs"),
|
|
367
|
+
`import fs from "node:fs";\n` +
|
|
368
|
+
`export const state = (v) => {\n` +
|
|
369
|
+
` if (!fs.existsSync(${JSON.stringify(drawing)})) {\n` +
|
|
370
|
+
` fs.writeFileSync(${JSON.stringify(drawing)}, String(process.pid));\n` +
|
|
371
|
+
` for (const end = Date.now() + 20_000; Date.now() < end && !fs.existsSync(${JSON.stringify(release)}); );\n` +
|
|
372
|
+
` }\n` +
|
|
373
|
+
` return JSON.stringify(v);\n` +
|
|
374
|
+
`};\n`,
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
const child = startCommand(dir, parent, { tmpdir });
|
|
378
|
+
const exited = once(child, "exit");
|
|
379
|
+
while (!fs.existsSync(drawing) || !read(drawing)) await sleep(20);
|
|
380
|
+
child.kill("SIGKILL");
|
|
381
|
+
await exited;
|
|
382
|
+
fs.writeFileSync(release, "");
|
|
383
|
+
await assertGone(-Number(read(drawing)));
|
|
384
|
+
await assertEmpty(parent);
|
|
385
|
+
await assertEmpty(tmpdir);
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
test(
|
|
390
|
+
"a check killed by a signal stops the command with exit 2, and it writes nothing",
|
|
391
|
+
{ timeout: 30_000 },
|
|
392
|
+
async () => {
|
|
393
|
+
const fakes = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-fakes-"));
|
|
394
|
+
const parent = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-parent-"));
|
|
395
|
+
const { java, pidFile } = sleepyJava(fakes);
|
|
396
|
+
const child = startCommand(path.join(corpus, "DieHard"), parent, { java });
|
|
397
|
+
let stderr = "";
|
|
398
|
+
child.stderr.on("data", (chunk: Buffer) => (stderr += chunk));
|
|
399
|
+
const closed = once(child, "close");
|
|
400
|
+
while (!fs.existsSync(pidFile) || !read(pidFile)) await sleep(20);
|
|
401
|
+
const ps = spawnSync("ps", ["-o", "ppid=", "-p", read(pidFile)], { encoding: "utf8" });
|
|
402
|
+
process.kill(Number(ps.stdout), "SIGKILL");
|
|
403
|
+
const [status] = await closed;
|
|
404
|
+
assert.equal(stderr, "SIGKILL killed the check, so the command wrote nothing.\n");
|
|
405
|
+
assert.equal(status, 2);
|
|
406
|
+
assert.deepEqual(fs.readdirSync(parent), []);
|
|
407
|
+
},
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
for (const signal of ["SIGINT", "SIGTERM"] satisfies NodeJS.Signals[]) {
|
|
411
|
+
test(
|
|
412
|
+
`${signal} stops TLC, removes the temporary folder, and writes nothing`,
|
|
413
|
+
{ timeout: 30_000 },
|
|
414
|
+
async () => {
|
|
415
|
+
const { pidFile } = await signalWhile(signal, (fakes) => {
|
|
416
|
+
const fake = sleepyJava(fakes);
|
|
417
|
+
return {
|
|
418
|
+
dir: path.join(corpus, "DieHard"),
|
|
419
|
+
...fake,
|
|
420
|
+
reached: () => fs.existsSync(fake.pidFile),
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
await assertGone(Number(read(pidFile)));
|
|
424
|
+
},
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
test(
|
|
428
|
+
`${signal} while the view draws the chart stops the command, and it writes nothing`,
|
|
429
|
+
{ timeout: 60_000 },
|
|
430
|
+
() =>
|
|
431
|
+
signalWhile(signal, (fakes) => {
|
|
432
|
+
const drawing = path.join(fakes, "drawing");
|
|
433
|
+
const dir = copyCase("Login", (d) =>
|
|
434
|
+
fs.writeFileSync(
|
|
435
|
+
path.join(d, "view.mjs"),
|
|
436
|
+
`import fs from "node:fs";\n` +
|
|
437
|
+
`export const state = (v) => {\n` +
|
|
438
|
+
` if (!fs.existsSync(${JSON.stringify(drawing)})) {\n` +
|
|
439
|
+
` fs.writeFileSync(${JSON.stringify(drawing)}, "");\n` +
|
|
440
|
+
` for (const end = Date.now() + 30_000; Date.now() < end; );\n` +
|
|
441
|
+
` }\n` +
|
|
442
|
+
` return JSON.stringify(v);\n` +
|
|
443
|
+
`};\n`,
|
|
444
|
+
),
|
|
445
|
+
);
|
|
446
|
+
return { dir, reached: () => fs.existsSync(drawing) };
|
|
447
|
+
}),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
test(
|
|
451
|
+
`${signal} while the command writes its files stops it, and it writes nothing`,
|
|
452
|
+
{ timeout: 60_000 },
|
|
453
|
+
() =>
|
|
454
|
+
signalWhile(signal, () => ({
|
|
455
|
+
dir: gridCase(150),
|
|
456
|
+
reached: (parent) => {
|
|
457
|
+
for (const f of fs.readdirSync(parent, { recursive: true, encoding: "utf8" }))
|
|
458
|
+
if (
|
|
459
|
+
path.basename(f) === "chart.mmd" &&
|
|
460
|
+
fs.statSync(path.join(parent, f), { throwIfNoEntry: false })?.size
|
|
461
|
+
)
|
|
462
|
+
return true;
|
|
463
|
+
return false;
|
|
464
|
+
},
|
|
465
|
+
})),
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
for (const signal of ["SIGINT", "SIGTERM"] satisfies NodeJS.Signals[]) {
|
|
470
|
+
test(`${signal} while the command reads TLC's last output stops the check and writes nothing`, () => {
|
|
471
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-late-"));
|
|
472
|
+
const java = path.join(tmp, "java.mjs");
|
|
473
|
+
const tmpdir = path.join(tmp, "tmpdir");
|
|
474
|
+
fs.mkdirSync(tmpdir);
|
|
475
|
+
const sendWhileRead = `const fs = require("node:fs");
|
|
476
|
+
const [dot, pid, signal, graph] = process.argv.slice(1);
|
|
477
|
+
setTimeout(() => process.exit(1), 30_000).unref();
|
|
478
|
+
fs.open(dot, "w", (error, fd) => {
|
|
479
|
+
process.kill(Number(pid), signal);
|
|
480
|
+
fs.writeSync(fd, fs.readFileSync(graph));
|
|
481
|
+
fs.closeSync(fd);
|
|
482
|
+
});`;
|
|
483
|
+
fs.writeFileSync(
|
|
484
|
+
java,
|
|
485
|
+
`#!/usr/bin/env node
|
|
486
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
487
|
+
const args = process.argv.slice(2);
|
|
488
|
+
const dot = args[args.indexOf("-dump") + 2] + ".dot";
|
|
489
|
+
spawnSync("mkfifo", [dot]);
|
|
490
|
+
const command = spawnSync("ps", ["-o", "ppid=", "-p", String(process.ppid)], { encoding: "utf8" }).stdout.trim();
|
|
491
|
+
spawn(process.execPath, ["-e", ${JSON.stringify(sendWhileRead)}, dot, command, ${JSON.stringify(signal)}, ${JSON.stringify(path.join(corpus, "Login", "graph.dot"))}], { detached: true, stdio: "ignore" }).unref();
|
|
492
|
+
`,
|
|
493
|
+
{ mode: 0o755 },
|
|
494
|
+
);
|
|
495
|
+
const r = run(path.join(corpus, "Login"), { java, env: { TMPDIR: tmpdir } });
|
|
496
|
+
assert.equal(r.stderr, `${signal} stopped the check, so the command wrote nothing.\n`);
|
|
497
|
+
assert.equal(r.status, 128 + os.constants.signals[signal]);
|
|
498
|
+
assert.deepEqual(fs.readdirSync(tmpdir), []);
|
|
499
|
+
assert.equal(fs.existsSync(r.out), false);
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
test("a broken property is named in the report, and its TLC trace is in tlc-NAME.out", () => {
|
|
504
|
+
const dir = path.join(corpus, "MediaPlayerProps");
|
|
505
|
+
const r = run(dir);
|
|
506
|
+
assert.equal(r.status, 1);
|
|
507
|
+
for (const p of ["EventuallyPlays", "QuietWhenStopped"]) {
|
|
508
|
+
assert.match(r.report, new RegExp(`^## ${p} breaks\n.*tlc-${p}\\.out`, "m"));
|
|
509
|
+
const out = read(path.join(r.out, `tlc-${p}.out`));
|
|
510
|
+
const from: string[] = [];
|
|
511
|
+
for (const name of Object.keys(r.configs)) {
|
|
512
|
+
const tool = name.startsWith("verdict-") && read(path.join(dir, `${name}.out`));
|
|
513
|
+
if (tool && out === `${[...plainText(readMessages(tool))].join("")}\n`) from.push(name);
|
|
514
|
+
}
|
|
515
|
+
assert.equal(from.length, 1, p);
|
|
516
|
+
assert.match(r.configs[from[0]], new RegExp(`^PROPERTY ${p}$`, "m"));
|
|
517
|
+
assert.match(out, /^TLC2 Version /);
|
|
518
|
+
assert.doesNotMatch(out, /@!@!@/);
|
|
519
|
+
}
|
|
520
|
+
assert.match(read(path.join(r.out, "tlc-QuietWhenStopped.out")), /^State 2: <YouTurnItUp /m);
|
|
521
|
+
assert.match(r.report, /^## Rules that hold\n.*\bEventuallyStops\b.*\bStaysTyped\b/m);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
test("TLC checks the rules and draws the chart with VIEW and SYMMETRY, and the report says an arrow can lead to a representative", () => {
|
|
525
|
+
const dir = copyCase("MediaPlayer", (d) =>
|
|
526
|
+
fs.appendFileSync(path.join(d, "MediaPlayer.cfg"), "\nVIEW vars\nSYMMETRY Perms\n"),
|
|
527
|
+
);
|
|
528
|
+
const r = run(dir);
|
|
529
|
+
assert.equal(r.status, 1);
|
|
530
|
+
const verdict = r.configs["verdict-1"];
|
|
531
|
+
assert.match(verdict, /^PROPERTY EventuallyStops$/m);
|
|
532
|
+
for (const config of [verdict, r.configs.ChartRun]) {
|
|
533
|
+
assert.match(config, /^VIEW vars$/m);
|
|
534
|
+
assert.match(config, /^SYMMETRY Perms$/m);
|
|
535
|
+
}
|
|
536
|
+
assert.match(
|
|
537
|
+
r.report,
|
|
538
|
+
/^With VIEW and SYMMETRY, TLC keeps one representative for each group of states that it counts as the same\. An arrow can lead to the representative rather than to the state that the step reaches\.$/m,
|
|
539
|
+
);
|
|
540
|
+
assert.doesNotMatch(run(path.join(corpus, "MediaPlayer")).report, /representative/);
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const overBudget = (file: string) =>
|
|
544
|
+
fs.writeFileSync(file, read(file).replace(/\b\d+( distinct states found)/g, "1234$1"));
|
|
545
|
+
|
|
546
|
+
test("a check above the chart budget skips the drawing run and draws only the bug paths", () => {
|
|
547
|
+
const dir = copyCase("FirstRule", (d) => {
|
|
548
|
+
for (const f of fs.readdirSync(d))
|
|
549
|
+
if (/^verdict-\d+\.out$/.test(f)) overBudget(path.join(d, f));
|
|
550
|
+
});
|
|
551
|
+
const r = run(dir);
|
|
552
|
+
assert.equal(r.status, 1);
|
|
553
|
+
assert.equal(r.configs.ChartRun, undefined);
|
|
554
|
+
assert.ok(r.files.includes("chart.mmd"));
|
|
555
|
+
assert.match(
|
|
556
|
+
r.report,
|
|
557
|
+
/^TLC reached more states than the chart budget of 500, so the chart draws only the bug paths: 4 states and 3 arrows\.$/m,
|
|
558
|
+
);
|
|
559
|
+
for (const rule of ["NotOne", "NotOneOrThree", "AlsoNotOne", "NoDeadlock"])
|
|
560
|
+
assert.match(r.report, new RegExp(`^## ${rule} breaks$`, "m"));
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
test("a passing check above the chart budget writes no chart.mmd and gives the state count", () => {
|
|
564
|
+
const dir = copyCase("Login", (d) => overBudget(path.join(d, "verdict-1.out")));
|
|
565
|
+
const r = run(dir);
|
|
566
|
+
assert.equal(r.status, 0);
|
|
567
|
+
assert.equal(r.configs.ChartRun, undefined);
|
|
568
|
+
assert.deepEqual(r.files, ["report.md", "tlc.out"]);
|
|
569
|
+
assert.match(
|
|
570
|
+
r.report,
|
|
571
|
+
/^TLC reached 1,234 states, more than the chart budget of 500, so the command wrote no chart\.mmd\.$/m,
|
|
572
|
+
);
|
|
573
|
+
assert.match(r.report, /^## Rules that hold\nTypeOK, NoDeadlock$/m);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
test("a property that INSTANCE actions break above the chart budget draws their qualified names and the loop", () => {
|
|
577
|
+
const dir = copyCase("CounterInstance", (d) => overBudget(path.join(d, "verdict-1.out")));
|
|
578
|
+
const r = run(dir);
|
|
579
|
+
assert.equal(r.status, 1);
|
|
580
|
+
assert.equal(r.configs.ChartRun, undefined);
|
|
581
|
+
assert.match(
|
|
582
|
+
r.report,
|
|
583
|
+
/^1\. C!Up: 1\n2\. C!Up: 2\n3\. C!Back: 1\nThe run loops through "1" and "2" forever\.$/m,
|
|
584
|
+
);
|
|
585
|
+
assert.match(read(path.join(r.out, "chart.mmd")), /^ s\d+ --> s\d+: C!Back$/m);
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
test("a drawing run that counts more states than the chart budget gives only the bug paths", () => {
|
|
589
|
+
const dir = copyCase("FirstRule", (d) => overBudget(path.join(d, "ChartRun.out")));
|
|
590
|
+
const r = run(dir);
|
|
591
|
+
assert.equal(r.status, 1);
|
|
592
|
+
assert.match(r.configs.ChartRun, /^CONSTRAINT ChartBudget$/m);
|
|
593
|
+
assert.match(
|
|
594
|
+
r.report,
|
|
595
|
+
/^TLC reached more states than the chart budget of 500, so the chart draws only the bug paths: 4 states and 3 arrows\.$/m,
|
|
596
|
+
);
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
test("a property with no temporal operator says TLC checked it only in the initial states", () => {
|
|
600
|
+
const r = run(path.join(corpus, "Counter"));
|
|
601
|
+
assert.equal(r.status, 0);
|
|
602
|
+
assert.match(r.report, /^## Rules that hold\nLow, StaysSmall\n/m);
|
|
603
|
+
assert.match(
|
|
604
|
+
r.report,
|
|
605
|
+
/^TLC checked Low only in the initial states, because it has no \[\], <>, or ~>\. List it as an INVARIANT to check every state\.$/m,
|
|
606
|
+
);
|
|
607
|
+
assert.doesNotMatch(r.report, /TLC checked StaysSmall/);
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
test("a property that the config replaces with a state predicate gets the initial-states note", () => {
|
|
611
|
+
const dir = copyCase("Counter", (d) =>
|
|
612
|
+
fs.writeFileSync(
|
|
613
|
+
path.join(d, "Counter.cfg"),
|
|
614
|
+
"SPECIFICATION Spec\nCONSTANT StaysSmall <- Low\nPROPERTY StaysSmall\nCHECK_DEADLOCK FALSE\n",
|
|
615
|
+
),
|
|
616
|
+
);
|
|
617
|
+
const r = run(dir);
|
|
618
|
+
assert.equal(r.status, 0);
|
|
619
|
+
assert.match(r.report, /^TLC checked StaysSmall only in the initial states, /m);
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
test("a property that an extended module also defines as LOCAL takes the root definition's level", () => {
|
|
623
|
+
const r = run(path.join(corpus, "Shadow"));
|
|
624
|
+
assert.equal(r.status, 0);
|
|
625
|
+
assert.doesNotMatch(r.report, /TLC checked Shadow/);
|
|
626
|
+
assert.match(r.report, /^TLC checked Shade only in the initial states, /m);
|
|
627
|
+
assert.match(r.report, /^TLC checked Hold only in the initial states, /m);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
test("a property from an extended module gets the initial-states note too", () => {
|
|
631
|
+
const dir = copyCase("MediaPlayerProps", (d) => {
|
|
632
|
+
fs.appendFileSync(path.join(d, "MediaPlayerProps.cfg"), "\nPROPERTY TypeOK\n");
|
|
633
|
+
fs.copyFileSync(path.join(d, "verdict-6.out"), path.join(d, "verdict-7.out"));
|
|
634
|
+
});
|
|
635
|
+
const r = run(dir);
|
|
636
|
+
assert.match(r.report, /^TLC checked TypeOK only in the initial states, /m);
|
|
637
|
+
assert.doesNotMatch(r.report, /TLC checked (EventuallyStops|StaysTyped)/);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
test("a spec that SANY cannot read stops the command with exit 2", () => {
|
|
641
|
+
const dir = copyCase("MediaPlayer", (d) => fs.writeFileSync(path.join(d, "sany.exit"), "150\n"));
|
|
642
|
+
const r = run(dir);
|
|
643
|
+
assert.equal(r.status, 2);
|
|
644
|
+
assert.match(r.stderr, /SANY could not read .*MediaPlayer\.tla \(exit 150\)/);
|
|
645
|
+
assert.deepEqual(r.files, []);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
test("a first verdict run that TLC cannot finish stops the command with exit 2", () => {
|
|
649
|
+
const dir = copyCase("Login", (d) => fs.writeFileSync(path.join(d, "verdict-1.exit"), "150\n"));
|
|
650
|
+
const r = run(dir);
|
|
651
|
+
assert.equal(r.status, 2);
|
|
652
|
+
assert.match(r.stderr, /TLC could not check .*Login\.tla \(exit 150\)/);
|
|
653
|
+
assert.deepEqual(r.files, []);
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
test("a later verdict run that TLC cannot finish keeps the exit and leaves its rules not checked", () => {
|
|
657
|
+
const dir = copyCase("CounterJump", (d) =>
|
|
658
|
+
fs.writeFileSync(path.join(d, "verdict-2.exit"), "150\n"),
|
|
659
|
+
);
|
|
660
|
+
const r = run(dir);
|
|
661
|
+
assert.equal(r.status, 1);
|
|
662
|
+
assert.match(r.report, /^## NotThree breaks$/m);
|
|
663
|
+
assert.match(
|
|
664
|
+
r.report,
|
|
665
|
+
/^## Rules not checked\nNoDeadlock\nTLC stopped with exit 150 before it checked this rule:$/m,
|
|
666
|
+
);
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
test("a drawing run that TLC cannot finish stops the command with exit 2", () => {
|
|
670
|
+
const dir = copyCase("Login", (d) => {
|
|
671
|
+
fs.rmSync(path.join(d, "graph.dot"));
|
|
672
|
+
fs.writeFileSync(path.join(d, "ChartRun.exit"), "150\n");
|
|
673
|
+
});
|
|
674
|
+
const r = run(dir);
|
|
675
|
+
assert.equal(r.status, 2);
|
|
676
|
+
assert.match(r.stderr, /TLC could not check .*Login\.tla \(exit 150\)/);
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
test("a java that cannot start stops the command with exit 2 on a runtime that emits no close for it", () => {
|
|
680
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "statechart-no-java-"));
|
|
681
|
+
const noClose = path.join(tmp, "no-close.mjs");
|
|
682
|
+
fs.writeFileSync(
|
|
683
|
+
noClose,
|
|
684
|
+
`import childProcess from "node:child_process";
|
|
685
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
686
|
+
const { spawn } = childProcess;
|
|
687
|
+
childProcess.spawn = (...args) => {
|
|
688
|
+
const child = spawn(...args);
|
|
689
|
+
const { emit } = child;
|
|
690
|
+
child.emit = (name, ...rest) =>
|
|
691
|
+
name === "close" && child.pid === undefined ? false : emit.call(child, name, ...rest);
|
|
692
|
+
return child;
|
|
693
|
+
};
|
|
694
|
+
syncBuiltinESMExports();
|
|
695
|
+
`,
|
|
696
|
+
);
|
|
697
|
+
const java = path.join(tmp, "java");
|
|
698
|
+
const spec = path.join(corpus, "Login", "Login.tla");
|
|
699
|
+
const r = run(path.dirname(spec), {
|
|
700
|
+
java,
|
|
701
|
+
command: [
|
|
702
|
+
process.execPath,
|
|
703
|
+
`--import=${pathToFileURL(noClose).href}`,
|
|
704
|
+
path.join(here, "../statechart.ts"),
|
|
705
|
+
],
|
|
706
|
+
});
|
|
707
|
+
assert.equal(r.stderr, `SANY could not read ${spec} (exit null):\nspawn ${java} ENOENT\n`);
|
|
708
|
+
assert.equal(r.status, 2);
|
|
709
|
+
assert.equal(fs.existsSync(r.out), false);
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
test("a step hidden by the view is not drawn, and the report counts it", () => {
|
|
713
|
+
const dir = path.join(corpus, "MediaPlayer");
|
|
714
|
+
const hidden = new Set(read(path.join(dir, "graph.dot")).match(/^.* -> .*label="ACallComesIn"/gm))
|
|
715
|
+
.size;
|
|
716
|
+
const view = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-view-")), "view.mjs");
|
|
717
|
+
fs.writeFileSync(
|
|
718
|
+
view,
|
|
719
|
+
`export * from ${JSON.stringify(path.join(dir, "view.mjs"))};\n` +
|
|
720
|
+
`import { step as words } from ${JSON.stringify(path.join(dir, "view.mjs"))};\n` +
|
|
721
|
+
`export const step = (action, from, to) => action === "ACallComesIn" ? null : words(action, from, to);\n`,
|
|
722
|
+
);
|
|
723
|
+
const r = run(dir, { view });
|
|
724
|
+
assert.equal(r.status, 1);
|
|
725
|
+
assert.match(
|
|
726
|
+
r.report,
|
|
727
|
+
new RegExp(`^The view hides ${hidden} steps, so the chart does not draw them\\.$`, "m"),
|
|
728
|
+
);
|
|
729
|
+
assert.doesNotMatch(read(path.join(r.out, "chart.mmd")), /a call comes in/);
|
|
730
|
+
assert.match(r.report, /^1\. you turn it up: Playback Stopped, Volume Loud$/m);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
test("a view that hides a step of a bug path stops the command with exit 2", () => {
|
|
734
|
+
const dir = path.join(corpus, "MediaPlayer");
|
|
735
|
+
const view = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-view-")), "view.mjs");
|
|
736
|
+
fs.writeFileSync(
|
|
737
|
+
view,
|
|
738
|
+
`export * from ${JSON.stringify(path.join(dir, "view.mjs"))};\n` +
|
|
739
|
+
`export const step = (action) => (action === "YouTurnItUp" ? null : action);\n`,
|
|
740
|
+
);
|
|
741
|
+
const r = run(dir, { view });
|
|
742
|
+
assert.equal(r.status, 2);
|
|
743
|
+
assert.match(r.stderr, /LoudWhenStopped/);
|
|
744
|
+
assert.deepEqual(r.files, []);
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
for (const [what, body, words] of [
|
|
748
|
+
["state() returns a number", "export const state = (v) => 7;", /state\(\) returned 7/],
|
|
749
|
+
["state() reads a missing variable", "export const state = (v) => `${v.nope}`;", /undefined/],
|
|
750
|
+
["step() returns an empty label", 'export const step = () => "";', /step\(\) returned ""/],
|
|
751
|
+
[
|
|
752
|
+
"group() names no color",
|
|
753
|
+
'export const group = () => "Busy";\nexport const colors = {};',
|
|
754
|
+
/group\(\) returned "Busy"/,
|
|
755
|
+
],
|
|
756
|
+
] satisfies [string, string, RegExp][]) {
|
|
757
|
+
test(`a view whose ${what} stops the command with exit 2`, () => {
|
|
758
|
+
const view = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-view-")), "view.mjs");
|
|
759
|
+
fs.writeFileSync(view, body);
|
|
760
|
+
const r = run(path.join(corpus, "Login"), { view });
|
|
761
|
+
assert.equal(r.status, 2);
|
|
762
|
+
assert.match(r.stderr, words);
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const writeView = (body: string) => {
|
|
767
|
+
const view = path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-view-")), "view.mjs");
|
|
768
|
+
fs.writeFileSync(view, body);
|
|
769
|
+
return view;
|
|
770
|
+
};
|
|
771
|
+
|
|
772
|
+
for (const [what, body, words] of [
|
|
773
|
+
[
|
|
774
|
+
"gives a group a mark class as its color",
|
|
775
|
+
'export const group = () => "Working";\nexport const colors = { Working: "breaks" };',
|
|
776
|
+
/\bWorking\b[\s\S]*"breaks"[\s\S]*\bblue\b[\s\S]*\bpink\b/,
|
|
777
|
+
],
|
|
778
|
+
[
|
|
779
|
+
"gives a group a color outside the theme",
|
|
780
|
+
'export const group = () => "Lost";\nexport const colors = { Lost: "red" };',
|
|
781
|
+
/^The view's colors give "Lost" the color "red"\. Give each group blue, orange, purple, yellow, teal, tan or pink\.\n$/,
|
|
782
|
+
],
|
|
783
|
+
[
|
|
784
|
+
"exports colors as a Map",
|
|
785
|
+
'export const colors = new Map([["Busy", "blue"]]);',
|
|
786
|
+
/^The view's colors must be a plain object\.\n$/,
|
|
787
|
+
],
|
|
788
|
+
[
|
|
789
|
+
"exports group() but no colors",
|
|
790
|
+
'export const group = () => "Working";',
|
|
791
|
+
/\bgroup\b[\s\S]*\bcolors\b/,
|
|
792
|
+
],
|
|
793
|
+
[
|
|
794
|
+
"misspells an export",
|
|
795
|
+
"export const state = (v) => v.main;\nexport const steps = (a) => a;",
|
|
796
|
+
/\bsteps\b[\s\S]*\bstep\b/,
|
|
797
|
+
],
|
|
798
|
+
["has a default export", "export default { state: (v) => v.main };", /\bdefault\b/],
|
|
799
|
+
["exports colors as a string", 'export const colors = "blue";', /\bcolors\b[\s\S]*\bobject\b/],
|
|
800
|
+
["exports a number as its title", "export const title = 7;", /\btitle\b[\s\S]*\bstring\b/],
|
|
801
|
+
] satisfies [string, string, RegExp][]) {
|
|
802
|
+
test(`a view that ${what} stops the command with exit 2 before TLC runs`, () => {
|
|
803
|
+
const r = run(path.join(corpus, "Login"), { view: writeView(body) });
|
|
804
|
+
assert.equal(r.status, 2);
|
|
805
|
+
assert.match(r.stderr, words);
|
|
806
|
+
assert.deepEqual(r.files, []);
|
|
807
|
+
assert.deepEqual(r.configs, {});
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
test("a view module that throws null stops the command with exit 2 and no stack trace", () => {
|
|
812
|
+
const r = run(path.join(corpus, "Login"), { view: writeView("throw null;\n") });
|
|
813
|
+
assert.equal(r.status, 2);
|
|
814
|
+
assert.equal(r.stderr, "null\n");
|
|
815
|
+
assert.deepEqual(r.files, []);
|
|
816
|
+
assert.deepEqual(r.configs, {});
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
test("a view module that throws an object with no prototype stops the command with exit 2", () => {
|
|
820
|
+
const r = run(path.join(corpus, "Login"), {
|
|
821
|
+
view: writeView("throw Object.create(null);\n"),
|
|
822
|
+
});
|
|
823
|
+
assert.equal(r.status, 2);
|
|
824
|
+
assert.equal(r.stderr, "[Object: null prototype] {}\n");
|
|
825
|
+
assert.deepEqual(r.files, []);
|
|
826
|
+
assert.deepEqual(r.configs, {});
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
test("a view module that throws a plain object with a message prints the message", () => {
|
|
830
|
+
const r = run(path.join(corpus, "Login"), {
|
|
831
|
+
view: writeView('throw { message: "The view has no states." };\n'),
|
|
832
|
+
});
|
|
833
|
+
assert.equal(r.status, 2);
|
|
834
|
+
assert.equal(r.stderr, "The view has no states.\n");
|
|
835
|
+
assert.deepEqual(r.files, []);
|
|
836
|
+
assert.deepEqual(r.configs, {});
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("a view that ends the check while TLC runs stops TLC, and the command exits 2 and writes nothing", async () => {
|
|
840
|
+
const { java, pidFile } = sleepyJava(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-fakes-")));
|
|
841
|
+
const r = run(path.join(corpus, "Login"), {
|
|
842
|
+
java,
|
|
843
|
+
view: writeView(
|
|
844
|
+
'import fs from "node:fs";\n' +
|
|
845
|
+
`setInterval(() => fs.existsSync(${JSON.stringify(pidFile)}) && process.exit(1), 20);\n` +
|
|
846
|
+
"export const state = (v) => JSON.stringify(v);\n",
|
|
847
|
+
),
|
|
848
|
+
});
|
|
849
|
+
assert.equal(r.status, 2);
|
|
850
|
+
assert.deepEqual(r.files, []);
|
|
851
|
+
await assertGone(Number(read(pidFile)));
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
test("a group that only a mixed chart state gets still needs a color", () => {
|
|
855
|
+
const view = writeView(
|
|
856
|
+
'export const state = (v) => v.main.split(".")[0];\n' +
|
|
857
|
+
'export const group = (v) => v.main.includes("Checking") ? "Checking" : v.main.includes("Wrong") ? "Waiting" : undefined;\n' +
|
|
858
|
+
'export const colors = { Waiting: "blue" };\n',
|
|
859
|
+
);
|
|
860
|
+
const r = run(path.join(corpus, "Login"), { view });
|
|
861
|
+
assert.equal(r.status, 2);
|
|
862
|
+
assert.match(r.stderr, /group\(\) returned "Checking"/);
|
|
863
|
+
assert.deepEqual(r.files, []);
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
test("a view with a group in a theme color draws that color and names the group in the Legend", () => {
|
|
867
|
+
const view = writeView(
|
|
868
|
+
'export const state = (v) => v.main.split(".");\n' +
|
|
869
|
+
'export const group = (v) => (v.main === "Signed in" ? "Done" : undefined);\n' +
|
|
870
|
+
'export const colors = { Done: "teal" };\n' +
|
|
871
|
+
'export const title = "Login";\n',
|
|
872
|
+
);
|
|
873
|
+
const r = run(path.join(corpus, "Login"), { view });
|
|
874
|
+
assert.equal(r.stderr, "");
|
|
875
|
+
assert.equal(r.status, 0);
|
|
876
|
+
const chart = read(path.join(r.out, "chart.mmd"));
|
|
877
|
+
assert.match(chart, /^title: "Login"$/m);
|
|
878
|
+
const id = chart.match(/^ {2}(s\d+): Signed in$/m)?.[1];
|
|
879
|
+
assert.match(chart, new RegExp(`^ {2}class ${id} teal$`, "m"));
|
|
880
|
+
const legend = chart.match(/^ {4}(lg\d+): Done$/m)?.[1];
|
|
881
|
+
assert.match(chart, new RegExp(`^ {2}class ${legend} teal$`, "m"));
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
const shown = (text: string) => text.replace(/#(\d+);/g, (_, n) => String.fromCodePoint(Number(n)));
|
|
885
|
+
|
|
886
|
+
const drawn = (chart: string) => {
|
|
887
|
+
const names = new Map<string, string>();
|
|
888
|
+
for (const m of chart.matchAll(/^ *(?:state "(.*)" as (s\d+) \{|(s\d+): (.*))$/gm))
|
|
889
|
+
names.set(m[2] ?? m[3], shown(m[1] ?? m[4]));
|
|
890
|
+
const arrows: string[] = [];
|
|
891
|
+
for (const [, from, to, text] of chart.matchAll(/^ *(s\d+) --> (s\d+): (.*)$/gm))
|
|
892
|
+
arrows.push(`${names.get(from)} --> ${names.get(to)}: ${shown(text)}`);
|
|
893
|
+
return { states: [...names.values()], arrows };
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
const byX = (labels: unknown[]) => `export const state = (v) => ${JSON.stringify(labels)}[v.x];\n`;
|
|
897
|
+
|
|
898
|
+
const fanOut = (n: number) => {
|
|
899
|
+
const lines = ["strict digraph DiskGraph {", '0 [label="x = 0",style = filled]'];
|
|
900
|
+
for (let i = 1; i <= n; i++)
|
|
901
|
+
lines.push(
|
|
902
|
+
`${i} [label="x = ${i}"];`,
|
|
903
|
+
`0 -> ${i} [label="Inc",color="black",fontcolor="black"];`,
|
|
904
|
+
);
|
|
905
|
+
return [...lines, "}"].join("\n");
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
test("a flat view names a label with U+0000 as written when a loop cannot take a step out of it", () => {
|
|
909
|
+
const view = writeView(
|
|
910
|
+
`export const state = (v) => (v.playback === "Stopped" || v.volume === "Muted" ? ${JSON.stringify("a\0b")} : v.playback);\n`,
|
|
911
|
+
);
|
|
912
|
+
const r = run(path.join(corpus, "MediaPlayerProps"), { view });
|
|
913
|
+
assert.equal(r.status, 2);
|
|
914
|
+
assert.equal(
|
|
915
|
+
r.stderr,
|
|
916
|
+
"The chart draws the step TheCallEnds out of a\0b, but the EventuallyPlays loop cannot take it there. Show in a label what stops it.\n",
|
|
917
|
+
);
|
|
918
|
+
assert.deepEqual(r.files, []);
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
for (const [what, c] of [
|
|
922
|
+
[
|
|
923
|
+
"a parent-state view keeps a label with U+0000 apart from the parent path it spells",
|
|
924
|
+
{
|
|
925
|
+
base: "Counter",
|
|
926
|
+
view: byX([["a\0b"], ["a", "b"], ["c"], ["d"]]),
|
|
927
|
+
status: 0,
|
|
928
|
+
states: ["ab", "a", "b", "c", "d"],
|
|
929
|
+
arrows: ["ab --> b: Inc", "b --> c: Inc", "c --> d: Inc"],
|
|
930
|
+
says: /^TLC reached 4 states and 3 steps\. The chart draws 4 states and 3 arrows\.$/m,
|
|
931
|
+
},
|
|
932
|
+
],
|
|
933
|
+
[
|
|
934
|
+
"a regions view keeps a label with U+0000 apart from the parent path it spells, and the report names it as written",
|
|
935
|
+
{
|
|
936
|
+
base: "Counter",
|
|
937
|
+
fanOut: 2,
|
|
938
|
+
view: byX([
|
|
939
|
+
{ R: "f", S: "p" },
|
|
940
|
+
{ R: ["a\0b"], S: "q" },
|
|
941
|
+
{ R: ["a", "b"], S: "r" },
|
|
942
|
+
]),
|
|
943
|
+
status: 0,
|
|
944
|
+
states: ["R f", "S p", "R ab", "S q", "R a", "R b", "S r"],
|
|
945
|
+
arrows: ["S p --> S q: Inc", "S p --> S r: Inc"],
|
|
946
|
+
says: /^The regions chart lets R f, S p go to R a\0b, S r by Inc, which the model never does\. So the chart draws parent states in place of regions\.$/m,
|
|
947
|
+
},
|
|
948
|
+
],
|
|
949
|
+
[
|
|
950
|
+
"a flat view with U+0001 in a label draws each arrow between the states it joins",
|
|
951
|
+
{
|
|
952
|
+
base: "Counter",
|
|
953
|
+
view: byX(["a\u0001b", "a", "b", "c"]),
|
|
954
|
+
status: 0,
|
|
955
|
+
states: ["a\u0001b", "a", "b", "c"],
|
|
956
|
+
arrows: ["a\u0001b --> a: Inc", "a --> b: Inc", "b --> c: Inc"],
|
|
957
|
+
says: /^TLC reached 4 states and 3 steps\. The chart draws 4 states and 3 arrows\.$/m,
|
|
958
|
+
},
|
|
959
|
+
],
|
|
960
|
+
[
|
|
961
|
+
"a parent-state view with U+0001 in a label draws the chart",
|
|
962
|
+
{
|
|
963
|
+
base: "Counter",
|
|
964
|
+
view: byX([["a\u0001b"], ["a", "b"], ["c"], ["d"]]),
|
|
965
|
+
status: 0,
|
|
966
|
+
states: ["a\u0001b", "a", "b", "c", "d"],
|
|
967
|
+
arrows: ["a\u0001b --> b: Inc", "b --> c: Inc", "c --> d: Inc"],
|
|
968
|
+
says: /^TLC reached 4 states and 3 steps\. The chart draws 4 states and 3 arrows\.$/m,
|
|
969
|
+
},
|
|
970
|
+
],
|
|
971
|
+
[
|
|
972
|
+
"a regions view with U+0001 in a label draws the chart",
|
|
973
|
+
{
|
|
974
|
+
base: "Counter",
|
|
975
|
+
view: byX([
|
|
976
|
+
{ R: "a\u0001b", S: "p" },
|
|
977
|
+
{ R: "c", S: "p" },
|
|
978
|
+
{ R: "d", S: "p" },
|
|
979
|
+
{ R: "e", S: "p" },
|
|
980
|
+
]),
|
|
981
|
+
status: 0,
|
|
982
|
+
states: ["a\u0001b", "c", "d", "e", "p"],
|
|
983
|
+
arrows: ["a\u0001b --> c: Inc", "c --> d: Inc", "d --> e: Inc"],
|
|
984
|
+
says: /^TLC reached 4 states and 3 steps\. The chart draws 5 states and 3 arrows\.$/m,
|
|
985
|
+
},
|
|
986
|
+
],
|
|
987
|
+
[
|
|
988
|
+
"a flat view reports a state with U+0002 in its label that breaks a rule",
|
|
989
|
+
{
|
|
990
|
+
base: "FirstRule",
|
|
991
|
+
view: byX(["a", "b\u0002c", "d", "e"]),
|
|
992
|
+
status: 1,
|
|
993
|
+
states: ["a", "b\u0002c", "d", "e"],
|
|
994
|
+
arrows: ["a --> b\u0002c: Next", "b\u0002c --> d: Next", "d --> e: Next"],
|
|
995
|
+
says: /^## NotOne breaks\nThe shortest path has 1 step:\n0\. a\n1\. Next: b\cBc$/m,
|
|
996
|
+
},
|
|
997
|
+
],
|
|
998
|
+
[
|
|
999
|
+
"a parent-state view reports a state with U+0002 in its label that breaks a rule",
|
|
1000
|
+
{
|
|
1001
|
+
base: "FirstRule",
|
|
1002
|
+
view: byX([["a"], ["P", "b\u0002c"], ["d"], ["e"]]),
|
|
1003
|
+
status: 1,
|
|
1004
|
+
states: ["a", "P", "b\u0002c", "d", "e"],
|
|
1005
|
+
arrows: ["a --> b\u0002c: Next", "b\u0002c --> d: Next", "d --> e: Next"],
|
|
1006
|
+
says: /^## NotOne breaks\nThe shortest path has 1 step:\n0\. a\n1\. Next: P \/ b\cBc$/m,
|
|
1007
|
+
},
|
|
1008
|
+
],
|
|
1009
|
+
[
|
|
1010
|
+
"a regions view reports a state with U+0002 in its label that breaks a rule",
|
|
1011
|
+
{
|
|
1012
|
+
base: "FirstRule",
|
|
1013
|
+
view: byX([
|
|
1014
|
+
{ R: "a", S: "p" },
|
|
1015
|
+
{ R: "b\u0002c", S: "p" },
|
|
1016
|
+
{ R: "d", S: "p" },
|
|
1017
|
+
{ R: "e", S: "p" },
|
|
1018
|
+
]),
|
|
1019
|
+
status: 1,
|
|
1020
|
+
states: ["a", "b\u0002c", "d", "e", "p"],
|
|
1021
|
+
arrows: ["a --> b\u0002c: Next", "b\u0002c --> d: Next", "d --> e: Next"],
|
|
1022
|
+
says: /^## NotOne breaks\nThe shortest path has 1 step:\n0\. R a, S p\n1\. Next: R b\cBc, S p$/m,
|
|
1023
|
+
},
|
|
1024
|
+
],
|
|
1025
|
+
[
|
|
1026
|
+
"a regions view with U+0003 in labels draws parent states when the regions chart would invent a move",
|
|
1027
|
+
{
|
|
1028
|
+
base: "Counter",
|
|
1029
|
+
fanOut: 4,
|
|
1030
|
+
view:
|
|
1031
|
+
byX([
|
|
1032
|
+
{ R: "f", S: "p" },
|
|
1033
|
+
{ R: "a", S: "r" },
|
|
1034
|
+
{ R: "b", S: "q" },
|
|
1035
|
+
{ R: "X\u0003a", S: "q" },
|
|
1036
|
+
{ R: "X\u0003b", S: "r" },
|
|
1037
|
+
]) +
|
|
1038
|
+
`export const step = (action, from, to) => (to.x <= 2 ? ${JSON.stringify("Go\u0003X")} : "Go");\n`,
|
|
1039
|
+
status: 0,
|
|
1040
|
+
states: ["R f", "S p", "R a", "S r", "R b", "S q", "R X\u0003a", "S q", "R X\u0003b", "S r"],
|
|
1041
|
+
arrows: [
|
|
1042
|
+
"S p --> S r: Go\u0003X",
|
|
1043
|
+
"S p --> S q: Go\u0003X",
|
|
1044
|
+
"S p --> S q: Go",
|
|
1045
|
+
"S p --> S r: Go",
|
|
1046
|
+
],
|
|
1047
|
+
says: /^The regions chart lets R f, S p go to R a, S q by Go\cCX, which the model never does\. So the chart draws parent states in place of regions\.$/m,
|
|
1048
|
+
},
|
|
1049
|
+
],
|
|
1050
|
+
] satisfies [
|
|
1051
|
+
string,
|
|
1052
|
+
{
|
|
1053
|
+
base: string;
|
|
1054
|
+
fanOut?: number;
|
|
1055
|
+
view: string;
|
|
1056
|
+
status: number;
|
|
1057
|
+
states: string[];
|
|
1058
|
+
arrows: string[];
|
|
1059
|
+
says: RegExp;
|
|
1060
|
+
},
|
|
1061
|
+
][]) {
|
|
1062
|
+
test(what, () => {
|
|
1063
|
+
const width = c.fanOut;
|
|
1064
|
+
const dir = width
|
|
1065
|
+
? copyCase(c.base, (d) => fs.writeFileSync(path.join(d, "graph.dot"), fanOut(width)))
|
|
1066
|
+
: path.join(corpus, c.base);
|
|
1067
|
+
const r = run(dir, { view: writeView(c.view) });
|
|
1068
|
+
assert.equal(r.stderr, "");
|
|
1069
|
+
assert.equal(r.status, c.status);
|
|
1070
|
+
assert.deepEqual(drawn(read(path.join(r.out, "chart.mmd"))), {
|
|
1071
|
+
states: c.states,
|
|
1072
|
+
arrows: c.arrows,
|
|
1073
|
+
});
|
|
1074
|
+
assert.match(r.report, c.says);
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
for (const [what, args, words] of [
|
|
1079
|
+
["an unknown option", ["--bogus"], /Unknown option '--bogus'[\s\S]*Usage:/],
|
|
1080
|
+
["a second spec", ["Other.tla"], /Give one SPEC\.tla\.[\s\S]*Usage:/],
|
|
1081
|
+
[
|
|
1082
|
+
"a config that does not exist",
|
|
1083
|
+
["--config", "Missing.cfg"],
|
|
1084
|
+
/Cannot find the TLC config Missing\.cfg/,
|
|
1085
|
+
],
|
|
1086
|
+
] satisfies [string, string[], RegExp][]) {
|
|
1087
|
+
test(`${what} stops the command with exit 2 before TLC runs`, () => {
|
|
1088
|
+
const r = run(path.join(corpus, "Login"), { args });
|
|
1089
|
+
assert.equal(r.status, 2);
|
|
1090
|
+
assert.match(r.stderr, words);
|
|
1091
|
+
assert.deepEqual(r.configs, {});
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
const contents = (p: string) => {
|
|
1096
|
+
const stat = fs.lstatSync(p);
|
|
1097
|
+
if (stat.isSymbolicLink()) return fs.readlinkSync(p);
|
|
1098
|
+
if (!stat.isDirectory()) return read(p);
|
|
1099
|
+
const files: { [name: string]: string } = {};
|
|
1100
|
+
for (const f of fs.readdirSync(p)) files[f] = read(path.join(p, f));
|
|
1101
|
+
return files;
|
|
1102
|
+
};
|
|
1103
|
+
const newPath = () => path.join(fs.mkdtempSync(path.join(os.tmpdir(), "statechart-out-")), "out");
|
|
1104
|
+
|
|
1105
|
+
for (const [what, make, words] of [
|
|
1106
|
+
[
|
|
1107
|
+
"a folder of the user's files",
|
|
1108
|
+
(out) => {
|
|
1109
|
+
fs.mkdirSync(out);
|
|
1110
|
+
fs.writeFileSync(path.join(out, ".bashrc"), "export PATH\n");
|
|
1111
|
+
fs.writeFileSync(path.join(out, "report.md"), "# My quarterly report\n");
|
|
1112
|
+
fs.writeFileSync(path.join(out, "tlc-Build.out"), "build log\n");
|
|
1113
|
+
},
|
|
1114
|
+
/is not empty\. It has \.bashrc\./,
|
|
1115
|
+
],
|
|
1116
|
+
[
|
|
1117
|
+
"a folder that holds only a dotfile",
|
|
1118
|
+
(out) => {
|
|
1119
|
+
fs.mkdirSync(out);
|
|
1120
|
+
fs.writeFileSync(path.join(out, ".bashrc"), "export PATH\n");
|
|
1121
|
+
},
|
|
1122
|
+
/is not empty\. It has \.bashrc\./,
|
|
1123
|
+
],
|
|
1124
|
+
[
|
|
1125
|
+
"the folder of an earlier run",
|
|
1126
|
+
(out) =>
|
|
1127
|
+
assert.equal(run(path.join(corpus, "MediaPlayerProps"), { args: ["--out", out] }).status, 1),
|
|
1128
|
+
/is not empty\. It has chart\.mmd\./,
|
|
1129
|
+
],
|
|
1130
|
+
[
|
|
1131
|
+
"a file",
|
|
1132
|
+
(out) => fs.writeFileSync(out, "keep me\n"),
|
|
1133
|
+
/cannot be the output folder\. ENOTDIR: not a directory/,
|
|
1134
|
+
],
|
|
1135
|
+
[
|
|
1136
|
+
"a broken link",
|
|
1137
|
+
(out) => fs.symlinkSync(path.join(path.dirname(out), "gone"), out),
|
|
1138
|
+
/cannot be the output folder\. It is a broken link\./,
|
|
1139
|
+
],
|
|
1140
|
+
] satisfies [string, (out: string) => void, RegExp][]) {
|
|
1141
|
+
test(`an --out that is ${what} stops the command with exit 2 before TLC runs, and keeps what is there`, () => {
|
|
1142
|
+
const out = newPath();
|
|
1143
|
+
make(out);
|
|
1144
|
+
const before = contents(out);
|
|
1145
|
+
const r = run(path.join(corpus, "Login"), { args: ["--out", out] });
|
|
1146
|
+
assert.equal(r.status, 2);
|
|
1147
|
+
assert.ok(r.stderr.includes(out), r.stderr);
|
|
1148
|
+
assert.match(r.stderr, words);
|
|
1149
|
+
assert.match(r.stderr, /Pass --out with a new folder\.\n$/);
|
|
1150
|
+
assert.deepEqual(r.configs, {});
|
|
1151
|
+
assert.deepEqual(contents(out), before);
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
test("a file that appears in --out while TLC runs is kept, and the command stops with exit 2", () => {
|
|
1156
|
+
const out = newPath();
|
|
1157
|
+
const mine = path.join(out, "report.md");
|
|
1158
|
+
const java = path.join(path.dirname(out), "java.sh");
|
|
1159
|
+
fs.writeFileSync(
|
|
1160
|
+
java,
|
|
1161
|
+
`#!/bin/sh\nmkdir -p ${JSON.stringify(out)}\n` +
|
|
1162
|
+
`[ -e ${JSON.stringify(mine)} ] || echo mine > ${JSON.stringify(mine)}\n` +
|
|
1163
|
+
`exec ${JSON.stringify(path.join(here, "java.ts"))} "$@"\n`,
|
|
1164
|
+
{ mode: 0o755 },
|
|
1165
|
+
);
|
|
1166
|
+
const r = run(path.join(corpus, "Login"), { args: ["--out", out], java });
|
|
1167
|
+
assert.equal(r.status, 2);
|
|
1168
|
+
assert.ok(r.stderr.includes(mine), r.stderr);
|
|
1169
|
+
assert.equal(read(mine), "mine\n");
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
test("an empty --out folder gets the chart and the report", () => {
|
|
1173
|
+
const out = newPath();
|
|
1174
|
+
fs.mkdirSync(out);
|
|
1175
|
+
const r = run(path.join(corpus, "Login"), { args: ["--out", out] });
|
|
1176
|
+
assert.equal(r.stderr, "");
|
|
1177
|
+
assert.equal(read(path.join(out, "report.md")), read(path.join(corpus, "Login", "report.md")));
|
|
1178
|
+
assert.deepEqual(fs.readdirSync(out).sort(), ["chart.mmd", "report.md", "tlc.out"]);
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
for (const [what, config, words] of [
|
|
1182
|
+
[
|
|
1183
|
+
"a misspelled first keyword",
|
|
1184
|
+
"\\* The rules\nINVARIENT NeverLocked\nSPECIFICATION Spec\n",
|
|
1185
|
+
/^TLC refuses .*Login\.cfg at line 2\. It was expecting a keyword, but did not find it\.$/m,
|
|
1186
|
+
],
|
|
1187
|
+
[
|
|
1188
|
+
"a CHECK_DEADLOCK value in lower case",
|
|
1189
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\nCHECK_DEADLOCK false\n",
|
|
1190
|
+
/Login\.cfg at line 3\. It was expecting TRUE or FALSE, but did not find it\./,
|
|
1191
|
+
],
|
|
1192
|
+
[
|
|
1193
|
+
"an ALIAS line",
|
|
1194
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\nVIEW vars\nALIAS Pretty\n",
|
|
1195
|
+
/Login\.cfg at line 4\. It was expecting a keyword, but did not find it\./,
|
|
1196
|
+
],
|
|
1197
|
+
[
|
|
1198
|
+
"a comma between invariants",
|
|
1199
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK, NeverLocked\n",
|
|
1200
|
+
/Login\.cfg at line 2\. The invariant , specified in the configuration file is not defined in the specification\./,
|
|
1201
|
+
],
|
|
1202
|
+
[
|
|
1203
|
+
"a keyword two times",
|
|
1204
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\nSPECIFICATION Spec\n",
|
|
1205
|
+
/Login\.cfg at line 3\. The keyword SPECIFICATION appeared twice\./,
|
|
1206
|
+
],
|
|
1207
|
+
[
|
|
1208
|
+
"a constant value that TLC cannot read",
|
|
1209
|
+
"CONSTANT N = -1\nSPECIFICATION Spec\n",
|
|
1210
|
+
/Login\.cfg at line 1\..*"-"/,
|
|
1211
|
+
],
|
|
1212
|
+
[
|
|
1213
|
+
"both SPECIFICATION and NEXT",
|
|
1214
|
+
"SPECIFICATION Spec\nNEXT Next\nINVARIANT TypeOK\n",
|
|
1215
|
+
/^TLC refuses the config .*Login\.cfg\. The configuration file cannot specify both INIT\/NEXT and SPECIFICATION fields\.$/m,
|
|
1216
|
+
],
|
|
1217
|
+
[
|
|
1218
|
+
"no SPECIFICATION and no INIT",
|
|
1219
|
+
"\\* The rules\nINVARIANT TypeOK\n",
|
|
1220
|
+
/^TLC refuses the config .*Login\.cfg\. The configuration file did not specify the initial state predicate\.$/m,
|
|
1221
|
+
],
|
|
1222
|
+
[
|
|
1223
|
+
"an operator constant with no arguments",
|
|
1224
|
+
"CONSTANT f() = 1\nSPECIFICATION Spec\n",
|
|
1225
|
+
/^TLC refuses .*Login\.cfg at line 1\. TLC gives no reason here\. An operator's arguments are one or more values in \( \), with commas between them\.$/m,
|
|
1226
|
+
],
|
|
1227
|
+
[
|
|
1228
|
+
"operator arguments with no closing parenthesis",
|
|
1229
|
+
"SPECIFICATION Spec\n\nCONSTANT f(1, 2 = 3\n",
|
|
1230
|
+
/^TLC refuses .*Login\.cfg at line 3\. TLC gives no reason here\. An operator's arguments are one or more values in \( \), with commas between them\.$/m,
|
|
1231
|
+
],
|
|
1232
|
+
[
|
|
1233
|
+
"a CONSTANT keyword on its own line before operator arguments with no closing parenthesis",
|
|
1234
|
+
"SPECIFICATION Spec\nCONSTANT\nf(1, 2\n= 3\n",
|
|
1235
|
+
/^TLC refuses .*Login\.cfg at line 2\. TLC gives no reason here\. An operator's arguments are one or more values in \( \), with commas between them\.$/m,
|
|
1236
|
+
],
|
|
1237
|
+
[
|
|
1238
|
+
"a comma invariant before a CHECK_DEADLOCK value in lower case",
|
|
1239
|
+
"SPECIFICATION Spec\nINVARIANT ,\nCHECK_DEADLOCK false\n",
|
|
1240
|
+
/Login\.cfg at line 3\. It was expecting TRUE or FALSE, but did not find it\./,
|
|
1241
|
+
],
|
|
1242
|
+
[
|
|
1243
|
+
"a comment that does not end",
|
|
1244
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\n(* The view\nVIEW vars\n",
|
|
1245
|
+
/Login\.cfg at line 3\..*\bcomment\b/,
|
|
1246
|
+
],
|
|
1247
|
+
[
|
|
1248
|
+
"CHECK_DEADLOCK FALSE in a comment that does not end",
|
|
1249
|
+
"INVARIANT TypeOK\nSPECIFICATION Spec\n(* CHECK_DEADLOCK FALSE\n",
|
|
1250
|
+
/Login\.cfg at line 3\. A comment starts here and does not end\./,
|
|
1251
|
+
],
|
|
1252
|
+
[
|
|
1253
|
+
"a no-break space before CHECK_DEADLOCK FALSE",
|
|
1254
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\n\u00a0CHECK_DEADLOCK FALSE\n",
|
|
1255
|
+
/Login\.cfg at line 3\. It cannot read the character "\u00a0" \(U\+00A0\) outside a string\./,
|
|
1256
|
+
],
|
|
1257
|
+
[
|
|
1258
|
+
"a line separator before CHECK_DEADLOCK FALSE",
|
|
1259
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\u2028CHECK_DEADLOCK FALSE\n",
|
|
1260
|
+
/Login\.cfg at line 2\. It cannot read the character "\u2028" \(U\+2028\) outside a string\./,
|
|
1261
|
+
],
|
|
1262
|
+
[
|
|
1263
|
+
"a byte order mark",
|
|
1264
|
+
"\uFEFFSPECIFICATION Spec\nINVARIANT TypeOK\n",
|
|
1265
|
+
/Login\.cfg at line 1\. The file starts with a byte order mark\. Save it as UTF-8 without one\.$/m,
|
|
1266
|
+
],
|
|
1267
|
+
] satisfies [string, string, RegExp][]) {
|
|
1268
|
+
test(`a config with ${what} stops the command with exit 2 before TLC runs`, () => {
|
|
1269
|
+
const dir = copyCase("Login", (d) => fs.writeFileSync(path.join(d, "Login.cfg"), config));
|
|
1270
|
+
const r = run(dir);
|
|
1271
|
+
assert.equal(r.status, 2);
|
|
1272
|
+
assert.match(r.stderr, words);
|
|
1273
|
+
assert.deepEqual(r.configs, {});
|
|
1274
|
+
assert.deepEqual(r.files, []);
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
test("a nested comment hides all of its text, as in TLC", () => {
|
|
1279
|
+
const dir = copyCase("Login", (d) => {
|
|
1280
|
+
const file = path.join(d, "Login.cfg");
|
|
1281
|
+
fs.writeFileSync(file, `(* Off: (* old *) CHECK_DEADLOCK FALSE *)\n${read(file)}`);
|
|
1282
|
+
});
|
|
1283
|
+
const r = run(dir);
|
|
1284
|
+
assert.equal(r.status, 0);
|
|
1285
|
+
assert.equal(r.report, read(path.join(corpus, "Login", "report.md")));
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
test("a string constant keeps the keywords and comment markers in it", () => {
|
|
1289
|
+
const dir = copyCase("TwoPhase", (d) =>
|
|
1290
|
+
fs.writeFileSync(
|
|
1291
|
+
path.join(d, "TwoPhase.cfg"),
|
|
1292
|
+
'CONSTANT RM = {"INVARIANT (*", "\\\\* r2"}\nSPECIFICATION TPSpec\nINVARIANT TPTypeOK TCConsistent\n',
|
|
1293
|
+
),
|
|
1294
|
+
);
|
|
1295
|
+
const r = run(dir);
|
|
1296
|
+
assert.equal(r.status, 1);
|
|
1297
|
+
assert.equal(r.report, read(path.join(corpus, "TwoPhase", "report.md")));
|
|
1298
|
+
assert.match(r.configs.ChartRun, /^CONSTANT RM = .*"INVARIANT \(\*".*"\\\\\* r2"/m);
|
|
1299
|
+
assert.deepEqual(r.configs.ChartRun.match(/^INVARIANT.*$/gm), ["INVARIANT ChartMark"]);
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
test("TYPE and TYPE_CONSTRAINT are config keywords, as in TLC", () => {
|
|
1303
|
+
const dir = copyCase("Login", (d) =>
|
|
1304
|
+
fs.writeFileSync(
|
|
1305
|
+
path.join(d, "Login.cfg"),
|
|
1306
|
+
"SPECIFICATION Spec\nINVARIANT TypeOK\nTYPE TypeOK\nTYPE_CONSTRAINT TypeOK\nVIEW vars\n",
|
|
1307
|
+
),
|
|
1308
|
+
);
|
|
1309
|
+
const r = run(dir);
|
|
1310
|
+
assert.equal(r.status, 0);
|
|
1311
|
+
assert.equal(r.report, read(path.join(corpus, "Login", "report.md")));
|
|
1312
|
+
assert.match(r.configs.ChartRun, /^TYPE TypeOK$/m);
|
|
1313
|
+
assert.match(r.configs.ChartRun, /^TYPE_CONSTRAINT TypeOK$/m);
|
|
1314
|
+
});
|