Testing
推論の品質は user-facing な入力と出力で固定し、実装の細部は必要な範囲だけ unit / integration test で守る。テストの説明は現在のルールだけを置き、追加履歴は残さない。
テスト層
| 層 | 守るもの |
|---|---|
| scenario | Ruby / RBS / RBI / project fixture から期待 RBS への user-facing 契約 |
| unit | 型演算、名前解決、merge、diagnostic 判定、上限などの局所不変条件 |
| CLI integration | 出力形式、file selection、diagnostics、debug、coverage option |
| LSP integration | protocol、snapshot、cache、refresh、incremental change |
| MCP integration | 実バイナリの起動、stdio protocol、tool discovery / call、推論結果 |
| robustness | 壊れた入力での panic / hang がないこと |
| performance | 速度・メモリの基準値と大規模 workspace の有界性 |
通常のゲート
./scripts/check.sh
git diff --checkcheck.sh はテストターゲット(lib / bins / 各 integration test / doc)を個別に実行し、 途中で失敗してもすべてのターゲットを走らせたうえで失敗一覧を末尾にまとめて表示する (fail-fast で後続ターゲットの失敗が隠れないようにするため)。
tests/mcp.rs は tyda mcp を子プロセスとして起動し、MCP client で stdio handshake、tool 一覧、 infer_type_at_position の呼び出しと構造化された推論結果を確認する。GitHub Actions の integration shard で実行する。
通常の Ruby scenario は analysis::analyze_source_for_display を入口とする完全解決の snapshot 経路を使う。この経路は LSP、playground、詳細 CLI の表示結果と共通であり、推論結果の差分を 入口ごとの実装で吸収しない。RBS / RBI / project fixture など、入力や解析 profile が異なる scenario はその条件を維持したうえで、同じ core の解析 backend を利用する。
通常の Ruby の表示 parity は analysis の unit test で tests/scenarios/ruby/ を in-process に走査し、 Playground の RBS / CodeLens / hover を canonical snapshot の projection と比較する。Rails / Sorbet / RBS / RBI / project fixture / 構文エラーを含むケースはこの対象から除外し、ブラウザや WASM を起動せずに 表示入口の差分を検出する。シナリオが増えたときも、テストはファイル単位で並列実行する。
入力途中の表示契約
tests/incremental_hover.rs は代表的な class / module / constant / method / parameter / local / ivar / literal の入力について、完成形へ向かうすべての UTF-8 prefix を解析する。型がその時点で 確定する位置以降は期待する hover を厳密に比較し、入力中の未完成 token や value-corrupting syntax error は untyped または抑制を許容する。後続の壊れた method が、先に確定した領域の hover を壊さ ないことも固定する。
LSP の lsp_hover_tracks_incremental_content_updates は同じ入力列を didChange で差し替え、 各状態の hover が古いキャッシュを返さないことを確認する。Playground と LSP の token hover は 同じ精密な snapshot 型を表示する。未注釈 method の call-site 引数は、public callable signature と メソッド本体の parameter、initialize から伝播する ivar で基底型へ widen する。一方、#: / sig / 外部 RBS・RBI で明示された parameter 型は保持する。 長い型の省略は hover 本文の表示だけに適用し、推論結果と RBS 出力は変更しない。 全シナリオの mutation test は引き続き panic / hang のないことを確認する層であり、意味的な期待値は このような明示的な incremental contract で追加する。
対象を絞るときは次を使う。
TYDA_SCENARIO_FILTER=<substring> cargo test -q --test scenario_runner -- --test-threads=1
cargo test --test <name>
cargo test <module>::<test>GitHub Actions では Test、Performance、Analysis compatibility、pages、release-vscode、Workflow lint をPRゲートとする。required checkはmatrix個別jobではなく、常設の test、performance、analysis-compatibility、pages、release-vscode、actionlint gateとする。各gateは対象scopeでは依存する全jobのsuccessを要求し、対象外scopeでは分類成功と依存jobの全skipを確認した場合だけsuccessとする。Test はLinuxとWindowsの Rust build / clippy / test(Windowsも全test targetを実行し、unit、軽量integration、mutation、pathological、docのshardを独立したrunnerで並列実行し、windows gateでformat / clippyと併せて集約する)、Performance は pinned な Ruby / Rails OSS subject ごとの base/head 比較、release workflowはgem / VSIXのLinux x86_64・Windows x64・Intel macOS・ ARM macOS package smoke testも確認する。Linux ARM64はrunnerの利用条件が整い次第追加する。
pages / e2e-test は npm run format:check、npm run typecheck、npm run lint を実行してからVitePressのドキュメント、Playgroundのwasm buildとE2Eを行う。E2EはPages artifactを /tyda/ 配下で配信し、Top・Docs・Playgroundの遷移、編集内容のhash付きreload、ページ間およびPlayground内のBack/Forward、壊れたhashからの復帰を確認する。pages gateが e2e-test の結果をrequired checkとして集約する。
Analysis compatibility は base commit と head commitをそれぞれのGemfile.lockから生成したRBSと組み合わせ、sample・pinned OSS subjectのCLI RBS出力とdiagnosticsをbyte単位で比較する。coverageも同じ組み合わせで比較し、差分があるPRはデフォルトで失敗する。意図した推論・coverage変更はJob SummaryのdiffをレビューしたMaintainer/Adminだけが approved-analysis-change ラベルで許可できる。ラベルはPerformance matrixのcoverage差分にも適用するが、実行時間・メモリの回帰は許可しない。新しいcommitではラベルを自動削除するため、古い承認を再利用できない。
scenario の期待 RBS は whitespace を正規化して比較するが、意味のない出力変更を許容する ための仕組みではない。出力を変えたときは意図を scenario / CLI test に残す。
Scenario の形式
1 ファイルは関連する 1 カテゴリ、1 case は 1 つの振る舞いにする。case 名は短い英語で書く。
# Ruby / method / example
## Infer a literal return
### update
```ruby
def answer = 42
```
### result
```rbs
def answer: -> 42
```case heading の直後には optional な YAML config を置ける。
ruby_version: "3.3"
rails_version: "7.1"
include_synthetic_dsl_methods: false
known_issue: falseupdate section では次を使える。
| block / marker | 用途 |
|---|---|
ruby | current file。複数 block は project file として扱う |
### file: path | 次の Ruby / SQL block の project path |
rbs | 外部 RBS input |
rbi | 外部 RBI input |
routes | config/routes.rb fixture |
schema / sql | Rails schema fixture |
result の rbs | 期待する RBS |
同じ case に複数の ### update を置く場合、各 update は累積差分ではなく、その時点の完全な入力状態を 表す。宣言を段階的に追加・削除するケースでは、各状態の期待 RBS を並べて優先順位と fallback を固定する。
project-backed case は config/、db/、app/models/ 以下の最小 fixture だけで構成する。 実在プロジェクトをそのまま fixture にせず、挙動が分かる最小コードへ縮める。
Scenario のカテゴリ
| カテゴリ | 例 |
|---|---|
ruby/class | namespace、継承、mixin、singleton、visibility |
ruby/control | narrowing、branch、rescue、loop |
ruby/literal | collection shape、補間、complexity cap |
ruby/method | return、param、block、recursion、dispatch |
ruby/runtime | Thread、Fiber、Queue、runtime fact |
ruby/variable | local、ivar、constant、pattern scope |
ruby/rbs_comment / rbs_input | inline / external RBS |
sorbet | comment、sig、RBI、T::Struct |
rails/ | DSL、routes、schema、framework plugin |
対応範囲の要約は capability-matrix.md に置く。未対応の user-facing 制約だけを roadmap に書き、テスト一覧を roadmap の代わりにしない。
外部コードを素材にする方針
外部プロジェクトやツールは、型結果との parity を測るためではなく、Ruby のコードパターンを 見つけるために使う。推論できるパターンは scenario にし、まだ扱えないパターンは安全な 縮退結果を確認するか、対応範囲として記録する。
解析対象の Ruby は必要性と信頼境界を見て扱う。挙動確認には必要な小さなコード片を隔離して実行する。 大規模 OSS はプログラムとして起動せず、静的解析・ドキュメント・最小再現例で調べる。性能計測では OSS を解析対象として扱う。
| 素材 | 収集するパターン |
|---|---|
| Ruby / Prism | 構文、AST 境界、壊れた入力、version gate |
| Ruby LSP | workspace、document change、definition、completion |
| Tapioca / RBS | generated RBI、schema、relation、宣言の合成 |
| Steep / Sorbet / TypeProf | comment、signature、generic、LSP の入口 |
| Rigor-type / Method-Ray / Type-Guessr | 実コードの型推定が難しい idiom |
| Redmine / Mastodon / GitLab | Rails DSL、巨大 workspace、in-tree extension |
Redmine の runtime DSL 登録は対象外とし、必要なら RBS / RBI fixture で表す。 GitLab の static extension は tests/scenarios/rails/dsl/gitlab_presenter.md、 Redmine の縮退契約は tests/scenarios/rails/dsl/redmine.md に置く。
LSP と diagnostics
- LSP test は protocol の形だけでなく、CLI と同じ query backend の結果を確認する。
didOpen/didChange後の Hover、CodeLens、diagnostics が最新 snapshot を見ることを固定する。- workspace scan 完了前の誤診断抑制、refresh の coalesce、full / incremental change を確認する。
- diagnostics は真陽性を先に固定し、Unknown を沈黙させる変更では false positive も確認する。
Robustness / performance
cargo test --release --test pathological_inputs
cargo test --test mutation_robustness
./scripts/benchmark.sh <path> [runs]
./scripts/benchmark_lsp.sh [runs] [subject] [release|debug]mutation test は壊れた入力の graceful degradation、pathological test は深い再帰・巨大 union・ 循環・自己参照 shape の有界性を確認する。基準値と計測ルールは performance.md に置く。
追加時のルール
- まず最小 scenario を追加し、同じ入力を複数のテスト層で重複させない。
- 期待 RBS は user-facing な契約だけを含め、内部用 synthetic method を無闇に表示しない。
- まだ推論できない入力も panic / hang しない結果を固定する。
- 正しい Ruby だが未対応の期待値は
tests/scenarios/known-issues/に置くか、case config にknown_issue: trueを付ける。最小の Ruby 例と Ruby semantics / RBS に基づく暫定期待値を残し、 推論が一致したら通常 scenario へ昇格する。 - 大きな推論変更は最小例から始め、対象 scenario、全 suite、OSS subject、benchmark へ段階的に広げる。
- flaky test は原因を再現・修正し、retry や skip で隠さない。
- Ruby version / Rails version による意味の差だけを version case にする。
- DSL は gem / framework ごとに分け、1 case に複数の無関係な機能を詰め込まない。
- source 固有の名前は一般化し、API 名や DSL 名など意味に必要な固有名だけ残す。
- scenario の意図が分からない自由文や作業履歴を追加しない。