## ADR-004 (Output/reports/ADR-004-sprint-25-39-decisions.md) Sprint 25~39 기간의 **15개 아키텍처 결정** 정리: - D1~D9: 거더교 MVP 확장 (단면 분기·다경간·Skew 관례·방호벽·격벽·Camber·헌치·UI) - D10~D13: IFC4X3 Add2 익스포터 4 결정 (크레이트 분리·형상 전략 3단계·GUID·Camber 근사) - D14: proc-macro 스캐폴딩 (전면 #[param] 는 Feature 10+ 안정 후) - D15: 변단면 거더 알고리즘 (소핏 lift + Y 선형보간) - 미결 6항목 (Pset 확장·LinearPlacement·ElementAssembly·IfcPile·#[param] 전면·변단면 IFC) - 테스트 커버리지 101개 현황표 ## IFC 스냅샷 테스트 (crates/ifc/tests/snapshot_tests.rs) insta 기반 회귀 방지, 8개 baseline: - mask_guids(): 22자 IFC GUID 를 'GUID' 로 정규화 (결정적 비교 가능) - 시나리오: 기본 단경간 PSC-I / 2경간 π형 / skew 15° / camber 50mm / Rectangle 단면 / parapets off - mask_guids 자체 유닛 테스트 2개 ## Mesh helper 유닛 테스트 (crates/viewer/src/bridge_scene.rs helper_tests) 순수 함수 9개 검증: - apply_camber_mesh: zero 항등·midspan 도달값·경간 밖 미영향 - rotate_y_around_z: 0 회전 항등·90° 피봇 회전·정점 개수 보존 - apply_variable_depth: zero 항등·소핏 lift · 지점 0 lift ## clippy lib 경고 15+ → 0 - map_identity (kernel/expansion_joint.rs) - unnecessary_lazy_evaluations ×4 (dsl/abutment·pier·csv_template — auto-fix) - too_many_arguments (usd save_scene — allow with justification) - clamp-like 패턴 ×7 (viewer bridge_scene/incremental_scene 의 .max(1).min(N) → .clamp(1, N)) - redundant_closure ×2 (project_file 의 `|e| Error::other(e)` → `Error::other`) - redundant_guard ×1 (viewer KeyboardInput match guard → 패턴 내 직접 매치) cargo clippy --workspace --lib: 0 경고. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
119 lines
3.2 KiB
Rust
119 lines
3.2 KiB
Rust
//! IFC 출력 스냅샷 테스트 (insta) — 회귀 방지용.
|
|
//!
|
|
//! IfcGUID 는 UUIDv4 무작위라 매 실행마다 달라지므로 `mask_guids()` 로
|
|
//! `GUID-XXX` 형태로 정규화한 뒤 스냅샷 비교.
|
|
//!
|
|
//! 실행:
|
|
//! ```bash
|
|
//! cargo test -p cimery-ifc --test snapshot_tests
|
|
//! # 새 baseline 채택: INSTA_UPDATE=always cargo test ...
|
|
//! ```
|
|
|
|
use cimery_ifc::{BridgeExportParams, IfcSectionKind, export_bridge};
|
|
|
|
/// GUID 정규화 — `'...'` 22자 IFC GUID 를 `'GUID'` 로 치환.
|
|
/// (IFC GUID charset: 0-9A-Za-z_$)
|
|
fn mask_guids(ifc: &str) -> String {
|
|
// 22자 = [0-9A-Za-z_$]{22} 로 정확 매치.
|
|
let mut out = String::with_capacity(ifc.len());
|
|
let chars: Vec<char> = ifc.chars().collect();
|
|
let mut i = 0;
|
|
while i < chars.len() {
|
|
if chars[i] == '\'' && i + 23 < chars.len() && chars[i + 23] == '\'' {
|
|
// i+1 ~ i+22 검사.
|
|
let all_guid_chars = (1..=22).all(|k| is_guid_char(chars[i + k]));
|
|
if all_guid_chars {
|
|
out.push_str("'GUID'");
|
|
i += 24;
|
|
continue;
|
|
}
|
|
}
|
|
out.push(chars[i]);
|
|
i += 1;
|
|
}
|
|
out
|
|
}
|
|
|
|
fn is_guid_char(c: char) -> bool {
|
|
c.is_ascii_alphanumeric() || c == '_' || c == '$'
|
|
}
|
|
|
|
#[test]
|
|
fn single_span_psc_i_default() {
|
|
let p = BridgeExportParams::default();
|
|
let ifc = export_bridge(&p);
|
|
insta::assert_snapshot!(mask_guids(&ifc));
|
|
}
|
|
|
|
#[test]
|
|
fn two_span_multi_column() {
|
|
let p = BridgeExportParams {
|
|
span_count: 2,
|
|
girder_count: 3,
|
|
..Default::default()
|
|
};
|
|
let ifc = export_bridge(&p);
|
|
insta::assert_snapshot!(mask_guids(&ifc));
|
|
}
|
|
|
|
#[test]
|
|
fn skewed_15_deg() {
|
|
let p = BridgeExportParams {
|
|
skew_deg: 15.0,
|
|
girder_count: 2,
|
|
..Default::default()
|
|
};
|
|
let ifc = export_bridge(&p);
|
|
insta::assert_snapshot!(mask_guids(&ifc));
|
|
}
|
|
|
|
#[test]
|
|
fn camber_50mm_subdivides() {
|
|
let p = BridgeExportParams {
|
|
camber_mid_mm: 50.0,
|
|
girder_count: 2,
|
|
..Default::default()
|
|
};
|
|
let ifc = export_bridge(&p);
|
|
insta::assert_snapshot!(mask_guids(&ifc));
|
|
}
|
|
|
|
#[test]
|
|
fn rectangle_section_no_profile_poly() {
|
|
let p = BridgeExportParams {
|
|
section_kind: IfcSectionKind::Rectangle,
|
|
girder_count: 2,
|
|
..Default::default()
|
|
};
|
|
let ifc = export_bridge(&p);
|
|
insta::assert_snapshot!(mask_guids(&ifc));
|
|
}
|
|
|
|
#[test]
|
|
fn parapets_off() {
|
|
let p = BridgeExportParams {
|
|
show_parapets: false,
|
|
girder_count: 2,
|
|
..Default::default()
|
|
};
|
|
let ifc = export_bridge(&p);
|
|
insta::assert_snapshot!(mask_guids(&ifc));
|
|
}
|
|
|
|
// ─── mask_guids 유닛 테스트 ───────────────────────────────────────────────
|
|
|
|
#[test]
|
|
fn mask_guids_replaces_22_char_token() {
|
|
let before = "IFCPROJECT('2JBHBMFGp5w3sCd6sd_Oz$',$,...)";
|
|
let after = mask_guids(before);
|
|
assert!(after.contains("'GUID'"));
|
|
assert!(!after.contains("2JBHBMFGp5w3sCd6sd_Oz$"));
|
|
}
|
|
|
|
#[test]
|
|
fn mask_guids_leaves_short_strings_alone() {
|
|
let before = "lit('Bridge Alignment')";
|
|
let after = mask_guids(before);
|
|
assert_eq!(before, after);
|
|
}
|