"""필드심볼·테이블본문 대입 파싱 검증. 이 문장 형태들이 토큰 3개로 쪼개지면 (a) 쓰기 지점이 누락되고 (b) 미인식 문장으로 잡힌다. ALV 필드카탈로그를 채우는 관용구라 프로그램에 따라 미인식률이 12% 까지 올라갔다. """ from pathlib import Path import pytest from parser.dataflow import base_symbol, extract_writes from parser.run import parse_program from parser.statements import assignment_eq_index, split_statements from parser.tokenizer import tokenize_code ROOT = Path(__file__).resolve().parent.parent NORM = ROOT / "data" / "normalized" def test_field_symbol_component_is_one_token(): assert tokenize_code("-fieldname = 'X'.")[:2] == ["-fieldname", "="] assert tokenize_code("->check_changed_data( ).")[0] == "->check_changed_data" assert tokenize_code("-a-b = 1.")[0] == "-a-b" def test_plain_field_symbol_unaffected(): assert tokenize_code("READ TABLE t ASSIGNING .")[-2] == "" assert tokenize_code(" = ls_y.")[:2] == ["", "="] def test_macro_param_tokenized(): assert tokenize_code("&1 = |{ &2 }|.")[0] == "&1" def test_base_symbol_normalizes_field_symbol(): assert base_symbol("-FIELDNAME") == "" assert base_symbol("->METH") == "" assert base_symbol("GS_HEAD-BELNR") == "GS_HEAD" @pytest.mark.parametrize( "code, expect_eq", [ ("lv_a = 1.", 1), ("-fieldname = 'X'.", 1), ("gt_tab[] = gt_src[].", 3), ("ls_r-opt[] = VALUE #( ).", 3), ("IF lv_a = 1.", None), # 조건문은 대입이 아니다 ("CLEAR lv_a.", None), ("PERFORM f USING a.", None), ], ) def test_assignment_eq_index(code, expect_eq): sts, _ = split_statements(code, "T") assert assignment_eq_index(sts[0].upper) == expect_eq def _writes_for(code: str): sts, _ = split_statements(code, "T") known = {"GT_TAB", "GT_SRC", "", "LS_R"} writes, _ = extract_writes(sts, list(range(len(sts))), "U", known) return {(w.symbol, w.kind) for w in writes} def test_field_symbol_component_write_recorded(): got = _writes_for("-fieldname = 'X'.") assert ("", "assign:field=FIELDNAME") in got def test_object_attribute_write_has_clean_field_name(): got = _writes_for("->html_control = x.") # '->' 가 field 이름에 '>' 로 새어 들어가지 않아야 한다 assert ("", "assign:field=HTML_CONTROL") in got def test_table_body_assignment_write_recorded(): got = _writes_for("gt_tab[] = gt_src[].") assert ("GT_TAB", "assign") in got def _normalized_dirs() -> list[Path]: return sorted(p for p in NORM.iterdir() if p.is_dir()) if NORM.exists() else [] @pytest.mark.skipif(not _normalized_dirs(), reason="정규화 산출물 없음") def test_unknown_ratio_low_across_all_programs(): """§4.6 — 한 프로그램만 보면 놓친다. 전체를 재고 최악값도 함께 본다. 특정 샘플 이름에 묶지 않는다 — 어떤 덤프가 들어와도 전체를 재야 의미가 있다. """ worst = [] for d in _normalized_dirs(): stats = parse_program(d)["stats"] worst.append((stats["unknown_ratio"], d.name)) worst.sort(reverse=True) top_ratio, top_name = worst[0] assert top_ratio < 0.03, f"미인식률 최악: {top_name} {top_ratio:.2%} (전체: {worst[:5]})"