feat(joinir): Phase 30.x jsonir v0 snapshot tests
Implement JSON snapshot testing for JoinIR regression detection. Implementation: - tests/fixtures/joinir/v0_*.jsonir: 6 snapshot fixtures for minimal JoinIR cases - src/tests/joinir_json_min.rs: Added 6 snapshot comparison tests Fixtures: - v0_skip_ws_min.jsonir (1338 bytes) - v0_funcscanner_trim_min.jsonir (4484 bytes) - v0_funcscanner_append_defs_min.jsonir (988 bytes) - v0_stage1_usingresolver_min.jsonir (987 bytes) - v0_stageb_body_min.jsonir (1074 bytes) - v0_stageb_funcscanner_min.jsonir (962 bytes) Usage: # Run snapshot tests NYASH_JOINIR_SNAPSHOT_TEST=1 cargo test --release joinir_json_v0_ # Regenerate fixtures (after intentional changes) NYASH_JOINIR_SNAPSHOT_TEST=1 NYASH_JOINIR_SNAPSHOT_GENERATE=1 cargo test --release joinir_json_v0_ Purpose: - Fix current JoinIR shape as v0 baseline (pre-generic state) - Detect unintended regressions during genericization - Allow intentional fixture updates when design changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -230,3 +230,217 @@ fn test_all_instruction_types_json() {
|
||||
|
||||
eprintln!("[joinir/json] all_instruction_types test passed");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Phase 30.x: jsonir v0 スナップショットテスト
|
||||
// ============================================================================
|
||||
//
|
||||
// 目的:
|
||||
// - 現状の JoinIR 形(pre-generic な generic_case_a / case-specific lowering)を
|
||||
// v0_ フィクスチャとして固定する
|
||||
// - JoinIR/LoopScopeShape の汎用化中に意図しない退行を検知する
|
||||
//
|
||||
// 実行方法:
|
||||
// NYASH_JOINIR_SNAPSHOT_TEST=1 cargo test --release joinir_json_v0_ -- --nocapture
|
||||
//
|
||||
// フィクスチャ初期生成:
|
||||
// NYASH_JOINIR_SNAPSHOT_GENERATE=1 cargo test --release joinir_json_v0_ -- --nocapture
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::lowering::{
|
||||
lower_funcscanner_append_defs_to_joinir, lower_funcscanner_trim_to_joinir,
|
||||
lower_skip_ws_to_joinir, lower_stage1_usingresolver_to_joinir, lower_stageb_body_to_joinir,
|
||||
lower_stageb_funcscanner_to_joinir,
|
||||
};
|
||||
use crate::mir::MirCompiler;
|
||||
use crate::parser::NyashParser;
|
||||
|
||||
/// フィクスチャファイルのベースディレクトリ
|
||||
const FIXTURE_DIR: &str = "tests/fixtures/joinir";
|
||||
|
||||
/// v0 スナップショットのケース定義
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum SnapshotCase {
|
||||
SkipWsMin,
|
||||
FuncscannerTrimMin,
|
||||
FuncscannerAppendDefsMin,
|
||||
Stage1UsingresolverMin,
|
||||
StagebBodyMin,
|
||||
StagebFuncscannerMin,
|
||||
}
|
||||
|
||||
impl SnapshotCase {
|
||||
fn fixture_filename(&self) -> &'static str {
|
||||
match self {
|
||||
Self::SkipWsMin => "v0_skip_ws_min.jsonir",
|
||||
Self::FuncscannerTrimMin => "v0_funcscanner_trim_min.jsonir",
|
||||
Self::FuncscannerAppendDefsMin => "v0_funcscanner_append_defs_min.jsonir",
|
||||
Self::Stage1UsingresolverMin => "v0_stage1_usingresolver_min.jsonir",
|
||||
Self::StagebBodyMin => "v0_stageb_body_min.jsonir",
|
||||
Self::StagebFuncscannerMin => "v0_stageb_funcscanner_min.jsonir",
|
||||
}
|
||||
}
|
||||
|
||||
fn source_file(&self) -> &'static str {
|
||||
match self {
|
||||
Self::SkipWsMin => "apps/tests/minimal_ssa_skip_ws.hako",
|
||||
Self::FuncscannerTrimMin => "lang/src/compiler/tests/funcscanner_trim_min.hako",
|
||||
Self::FuncscannerAppendDefsMin => "apps/tests/funcscanner_append_defs_minimal.hako",
|
||||
Self::Stage1UsingresolverMin => "apps/tests/stage1_usingresolver_minimal.hako",
|
||||
Self::StagebBodyMin => "apps/tests/stageb_body_extract_minimal.hako",
|
||||
Self::StagebFuncscannerMin => "apps/tests/stageb_funcscanner_scan_boxes_minimal.hako",
|
||||
}
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::SkipWsMin => "skip_ws_min",
|
||||
Self::FuncscannerTrimMin => "funcscanner_trim_min",
|
||||
Self::FuncscannerAppendDefsMin => "funcscanner_append_defs_min",
|
||||
Self::Stage1UsingresolverMin => "stage1_usingresolver_min",
|
||||
Self::StagebBodyMin => "stageb_body_min",
|
||||
Self::StagebFuncscannerMin => "stageb_funcscanner_min",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// ケースに対応する JoinIR JSON を生成
|
||||
fn generate_joinir_json(case: SnapshotCase) -> Option<String> {
|
||||
// Stage-3 parser を有効化
|
||||
std::env::set_var("NYASH_PARSER_STAGE3", "1");
|
||||
std::env::set_var("HAKO_PARSER_STAGE3", "1");
|
||||
|
||||
let src = std::fs::read_to_string(case.source_file()).ok()?;
|
||||
|
||||
// FuncScanner.trim は FuncScannerBox の定義が必要
|
||||
let full_src = if matches!(case, SnapshotCase::FuncscannerTrimMin) {
|
||||
let func_scanner_src =
|
||||
std::fs::read_to_string("lang/src/compiler/entry/func_scanner.hako").ok()?;
|
||||
format!("{func_scanner_src}\n\n{src}")
|
||||
} else {
|
||||
src
|
||||
};
|
||||
|
||||
let ast: ASTNode = NyashParser::parse_from_string(&full_src).ok()?;
|
||||
let mut mc = MirCompiler::with_options(false);
|
||||
let compiled = mc.compile(ast).ok()?;
|
||||
|
||||
let join_module = match case {
|
||||
SnapshotCase::SkipWsMin => lower_skip_ws_to_joinir(&compiled.module),
|
||||
SnapshotCase::FuncscannerTrimMin => lower_funcscanner_trim_to_joinir(&compiled.module),
|
||||
SnapshotCase::FuncscannerAppendDefsMin => {
|
||||
lower_funcscanner_append_defs_to_joinir(&compiled.module)
|
||||
}
|
||||
SnapshotCase::Stage1UsingresolverMin => {
|
||||
lower_stage1_usingresolver_to_joinir(&compiled.module)
|
||||
}
|
||||
SnapshotCase::StagebBodyMin => lower_stageb_body_to_joinir(&compiled.module),
|
||||
SnapshotCase::StagebFuncscannerMin => lower_stageb_funcscanner_to_joinir(&compiled.module),
|
||||
}?;
|
||||
|
||||
Some(join_module_to_json_string(&join_module))
|
||||
}
|
||||
|
||||
/// スナップショット比較を実行(共通ロジック)
|
||||
fn run_snapshot_test(case: SnapshotCase) {
|
||||
// トグルチェック
|
||||
if std::env::var("NYASH_JOINIR_SNAPSHOT_TEST").ok().as_deref() != Some("1") {
|
||||
eprintln!(
|
||||
"[joinir/snapshot] NYASH_JOINIR_SNAPSHOT_TEST=1 not set, skipping {}",
|
||||
case.name()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let fixture_path = format!("{}/{}", FIXTURE_DIR, case.fixture_filename());
|
||||
|
||||
// JoinIR JSON 生成
|
||||
let json = match generate_joinir_json(case) {
|
||||
Some(j) => j,
|
||||
None => {
|
||||
eprintln!(
|
||||
"[joinir/snapshot] Failed to generate JoinIR for {}, skipping",
|
||||
case.name()
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// フィクスチャ生成モード
|
||||
if std::env::var("NYASH_JOINIR_SNAPSHOT_GENERATE").ok().as_deref() == Some("1") {
|
||||
std::fs::write(&fixture_path, &json).expect("Failed to write fixture");
|
||||
eprintln!(
|
||||
"[joinir/snapshot] Generated fixture: {} ({} bytes)",
|
||||
fixture_path,
|
||||
json.len()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// フィクスチャ読み込み
|
||||
let fixture = match std::fs::read_to_string(&fixture_path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => {
|
||||
eprintln!(
|
||||
"[joinir/snapshot] Fixture not found: {}\n\
|
||||
Run with NYASH_JOINIR_SNAPSHOT_GENERATE=1 to create it.",
|
||||
fixture_path
|
||||
);
|
||||
panic!("Fixture not found: {}", fixture_path);
|
||||
}
|
||||
};
|
||||
|
||||
// 比較
|
||||
if json != fixture {
|
||||
eprintln!(
|
||||
"[joinir/snapshot] MISMATCH for {}\n\
|
||||
--- actual ({} bytes) ---\n{}\n\
|
||||
--- fixture ({} bytes) ---\n{}",
|
||||
case.name(),
|
||||
json.len(),
|
||||
json,
|
||||
fixture.len(),
|
||||
fixture
|
||||
);
|
||||
panic!("jsonir v0 snapshot mismatch for {}", case.name());
|
||||
}
|
||||
|
||||
eprintln!(
|
||||
"[joinir/snapshot] {} matches fixture ✓",
|
||||
case.name()
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// 個別スナップショットテスト
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn joinir_json_v0_skip_ws_min_matches_fixture() {
|
||||
run_snapshot_test(SnapshotCase::SkipWsMin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joinir_json_v0_funcscanner_trim_min_matches_fixture() {
|
||||
run_snapshot_test(SnapshotCase::FuncscannerTrimMin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joinir_json_v0_funcscanner_append_defs_min_matches_fixture() {
|
||||
run_snapshot_test(SnapshotCase::FuncscannerAppendDefsMin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joinir_json_v0_stage1_usingresolver_min_matches_fixture() {
|
||||
run_snapshot_test(SnapshotCase::Stage1UsingresolverMin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joinir_json_v0_stageb_body_min_matches_fixture() {
|
||||
run_snapshot_test(SnapshotCase::StagebBodyMin);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn joinir_json_v0_stageb_funcscanner_min_matches_fixture() {
|
||||
run_snapshot_test(SnapshotCase::StagebFuncscannerMin);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user