Skip to content

load.py 돌연변이 스윕 생존자 정리 — 삭제·완료 경로에 양성 테스트가 없다 (#95 형제) #166

Description

@epruseal

#95 는 이관 범위 두 모듈(assembler.py · neo4j_export.py)의 생존자를 다룬다. 그 이슈의 비교 표에는 normalize.py · schema.py · jsonl_io.py · build.py 만 있고 load.py 가 없다. 이 이슈가 그 자리를 채운다.

측정 (2026-08-11 15:18, 도구 최신 리비전)

opencrab/pack/load.py   총 910 / KILLED 505 / SURVIVED 403 / BROKEN 0 / HUNG 0 / INVALID 2

생존자 함수별 분포:

load_edges 58 · flush_single 50 · delete_pack 47 · incremental_finalize 40
load_nodes_incremental 37 · load_chunks_incremental 25 · _vec_meta_update 24
load_nodes 24 · flush 22 · live_pack_state 15 · _vec_backend 14
pack_live_counts 12 · load_chunks 5 · _batched 1

축별:

const 162 · del-stmt 137 · aug 22 · bin 19 · slice-upper 15 · cmp 13 · kwarg 5 · top-const 4 · call-target 1

삭제·완료 판정 경로 소계 152 / 403 (delete_pack · incremental_finalize · live_pack_state · pack_live_counts · _vec_backend · _vec_meta_update).

가장 무거운 생존자

증분의 stale 엣지 삭제 루프를 통째로 지워도 전 스위트가 초록이다. 음성 테스트(edge_del == 0)만 있어서, 아무것도 안 지워져도 통과한다. 삭제가 도는지 확인하는 양성 테스트가 없다.

이 이슈가 다루지 않는 것

#161 #162 #163 #164 #165실제 동작 결함이고 이 이슈는 테스트 강도 부채다. 축이 다르다.

다만 겹치는 부분이 있다: 삭제·완료 경로의 생존자 152건 중 일부는 판정할 계약 자체가 없어서 테스트를 쓸 수 없는 것이다. #161(적재 완료 판정 계약)이 들어오면 그 축의 생존자는 함께 닫힌다. 순서상 #161 이 먼저다.

#93 PR 에서 닫는 부분

stale 엣지 삭제의 양성 테스트 1건#93 PR 에 포함한다 — 그 PR 이 되돌리는 안전핀 회귀의 회귀 방지 테스트와 같은 자리이기 때문이다. 나머지 생존자는 이 이슈로 넘긴다.

처리 방침

#95 와 같다. 생존자를 미검사 경로등가 변이로 나누고, 등가는 추론이 아니라 측정으로 보인 뒤 전제를 주석에 못박는다.

const 162 · del-stmt 137 이 대부분인데, 이 둘은 사람에게만 보이는 로그 문구·진행 출력이 섞여 있다. 먼저 identity 생성 · 보호 집합 · 삭제 후보 · 삭제 인자 · 완료 판정으로 향하는 것만 골라내고, 그 밖은 등가로 분류하되 근거를 남긴다.

재현

# 클론에서 돌릴 것 — 도구가 대상 모듈을 변이시켰다가 되돌린다.
python scripts/qa/mutate_module.py . opencrab/pack/load.py tests/test_pack_load.py /tmp/load.json

# 함수별 분류
python3 - <<'PY'
import json, ast, pathlib, collections, re
surv = json.load(open("/tmp/load.json"))[0]["survived"]
src = pathlib.Path("opencrab/pack/load.py").read_text()
spans = [(n.lineno, n.end_lineno, n.name) for n in ast.walk(ast.parse(src))
         if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef))]
def fn(l):
    best = None
    for a, b, nm in spans:
        if a <= l <= b and (best is None or b - a < best[1] - best[0]):
            best = (a, b, nm)
    return best[2] if best else "<module>"
c = collections.Counter()
for s in surv:
    m = re.match(r"([a-z0-9\-]+):.*@L(\d+)$", s)
    if m:
        c[fn(int(m.group(2)))] += 1
for k, v in c.most_common():
    print(f"{v:4d}  {k}")
PY

관련

#95 (형제 — 이관 밖 두 모듈), #93 (이관), #161 (완료 판정 계약 — 선행)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions