""" テストギャップ穴埋め — 未検証モジュールの機能テスト ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 対象: hina.hina_agent, jcl.executor, jcl.parser """ import sys, json from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent)) PASS=0;FAIL=0;LOG=[] def do(cat,name,fn): global PASS,FAIL try: fn(); PASS+=1; LOG.append(f' [{cat}] {name} -> PASS') except Exception as e: FAIL+=1; LOG.append(f' [{cat}] {name} -> FAIL: {str(e)[:100]}') # ── hina.hina_agent: LLM応答パース ── from hina.hina_agent import _parse_llm_response, _validate_result, _fallback_classification, CONFUSION_PROMPT do('HAG','_parse_llm_response: 生JSON', lambda: ( r:=_parse_llm_response('{"category":"condition_heavy","confidence":0.85}'), r['category']=='condition_heavy' and r['confidence']==0.85)) do('HAG','_parse_llm_response: ```json ブロック', lambda: ( r:=_parse_llm_response('```json\n{"category":"data_file_centric","confidence":0.9}\n```'), r['category']=='data_file_centric' and r['confidence']==0.9)) do('HAG','_parse_llm_response: ``` ブロック(無json)', lambda: ( r:=_parse_llm_response('```\n{"category":"simple_sequential","confidence":0.7}\n```'), r['category']=='simple_sequential')) do('HAG','_parse_llm_response: 空文字', lambda: ( r:=_parse_llm_response(''), r['category']=='unknown')) do('HAG','_parse_llm_response: 無効JSON', lambda: ( r:=_parse_llm_response('not json at all'), r['category']=='unknown')) do('HAG','_validate_result: 最小値', lambda: ( r:=_validate_result({}), r['category']=='unknown' and r['confidence']==0.0 and r['required_tests']>=1)) do('HAG','_validate_result: 信頼度クランプ', lambda: ( r:=_validate_result({'confidence':5.0,'required_tests':0}), r['confidence']<=1.0 and r['required_tests']>=1)) do('HAG','_validate_result: 信頼度下限', lambda: ( r:=_validate_result({'confidence':-1.0}), r['confidence']>=0.0)) do('HAG','_validate_result: 不正タイプ', lambda: ( r:=_validate_result({'confidence':'abc','required_tests':'xyz'}), r['confidence']==0.0 and r['required_tests']>=1)) do('HAG','_fallback_classification: 分岐0', lambda: ( r:=_fallback_classification({'decision_points':[],'paragraphs':[],'file_count':0}), r['category']=='simple_sequential')) do('HAG','_fallback_classification: SEARCH ALL', lambda: ( r:=_fallback_classification({'decision_points':[{'kind':'IF'}],'paragraphs':[],'file_count':0,'has_search_all':True,'has_call':False,'has_break':False}), r['category']=='search_intensive')) do('HAG','_fallback_classification: CALLベース', lambda: ( r:=_fallback_classification({'decision_points':[{'kind':'IF'}],'paragraphs':[],'file_count':0,'has_search_all':False,'has_call':True,'has_break':False}), r['category']=='call_based')) do('HAG','_fallback_classification: mixed_complex', lambda: ( r:=_fallback_classification({'decision_points':[{'kind':'IF'}]*5,'paragraphs':[],'file_count':2,'has_search_all':True,'has_call':True,'has_break':True}), r['category']=='mixed_complex')) do('HAG','CONFUSION_PROMPT 書式', lambda: ( p:=CONFUSION_PROMPT.format(paragraph_count=3,decision_count=2,if_count=1, evaluate_count=1,file_count=1,open_directions='{}',has_search_all='false', has_call='false',has_break='false',total_branches=2), 'paragraph_count' not in p and 'IF' in p)) # ── jcl.parser: JCL解析 ── from jcl.parser import parse_jcl SAMPLE_JCL = """//CREDIT25 JOB (CRD),'MONTHLY BILLING',CLASS=A,MSGCLASS=X //STEP1 EXEC PGM=SORT //SORTIN DD DSN=TRANSACTIONS.DATA,DISP=SHR //SORTOUT DD DSN=SORTED.DATA,DISP=(NEW,PASS) //SYSIN DD * SORT FIELDS=(1,16,CH,A) //STEP2 EXEC PGM=CRDVAL,COND=(0,NE) //TRANSIN DD DSN=SORTED.DATA,DISP=(OLD,DELETE) //MEMBER DD DSN=MEMBER.DATA,DISP=SHR //VALIDOUT DD DSN=VALID.DATA,DISP=(NEW,CATLG) //REJECT DD SYSOUT=* //REPORTERR DD SYSOUT=* //STEP3 EXEC PGM=CRDCALC,COND=(0,NE) //VALIDIN DD DSN=VALID.DATA,DISP=(OLD,DELETE) //RATE DD DSN=RATE.DATA,DISP=SHR //CALCOUT DD DSN=CALC.DATA,DISP=(NEW,CATLG) //STEP4 EXEC PGM=CRDRPT,COND=(0,NE) //BILLING DD DSN=CALC.DATA,DISP=(OLD,DELETE) //STMT DD DSN=STMT.DATA,DISP=(NEW,CATLG) //SUMMARY DD DSN=SUMMARY.DATA,DISP=(NEW,CATLG) // DD SYSOUT=* """ do('JCL','parse_jcl 4STEP解析', lambda: ( j:=parse_jcl(SAMPLE_JCL), len(j.steps)==4)) do('JCL','JOB情報解析', lambda: ( j:=parse_jcl(SAMPLE_JCL), j.job_name=='CREDIT25' and j.job_class=='A')) do('JCL','STEP1:SORT PGM定義', lambda: ( j:=parse_jcl(SAMPLE_JCL), j.steps[0].program=='SORT' and j.steps[0].step_name=='STEP1')) do('JCL','DD定義:入力ファイル', lambda: ( j:=parse_jcl(SAMPLE_JCL), any('TRANSACTIONS' in d.dsn for d in j.steps[0].dd_list))) do('JCL','DD定義:出力ファイル', lambda: ( j:=parse_jcl(SAMPLE_JCL), any('VALID.DATA' in d.dsn for d in j.steps[1].dd_list))) do('JCL','CONDパラメータ', lambda: ( j:=parse_jcl(SAMPLE_JCL), j.steps[1].cond is not None and '0' in str(j.steps[1].cond))) do('JCL','SYSINインラインデータ', lambda: ( j:=parse_jcl(SAMPLE_JCL), len(j.steps[0].sysin_lines)>0 and 'SORT' in j.steps[0].sysin_lines[0])) do('JCL','SYSOUT出力', lambda: ( j:=parse_jcl(SAMPLE_JCL), any('*' in d.dsn for d in j.steps[1].dd_list))) do('JCL','空JCL', lambda: ( j:=parse_jcl(''), len(j.steps)==0)) do('JCL','コメント行スキップ', lambda: ( j:=parse_jcl('//* THIS IS COMMENT\n//STEP1 EXEC PGM=TEST\n'), len(j.steps)==1 and j.steps[0].program=='TEST')) # ── jcl.executor ── from jcl.executor import JclExecutor, CondEvaluator do('JEX','CondEvaluator: (0,NE)', lambda: ( CondEvaluator().evaluate('(0,NE)', 0)==False)) do('JEX','CondEvaluator: (0,NE) RC=4', lambda: ( CondEvaluator().evaluate('(0,NE)', 4)==True)) do('JEX','CondEvaluator: (0,GT) RC=0', lambda: ( CondEvaluator().evaluate('(0,GT)', 0)==False)) do('JEX','CondEvaluator: (0,GT) RC=4', lambda: ( CondEvaluator().evaluate('(0,GT)', 4)==True)) do('JEX','CondEvaluator: (4,LE) RC=4', lambda: ( CondEvaluator().evaluate('(4,LE)', 4)==True)) do('JEX','CondEvaluator: (4,LE) RC=8', lambda: ( CondEvaluator().evaluate('(4,LE)', 8)==False)) do('JEX','CondEvaluator: EVEN', lambda: ( CondEvaluator().evaluate('EVEN', 0)==True)) do('JEX','CondEvaluator: ONLY', lambda: ( CondEvaluator().evaluate('ONLY', 0)==True)) do('JEX','CondEvaluator: 空文字列', lambda: ( CondEvaluator().evaluate('', 0)==None)) do('JEX','JclExecutor インスタンス', lambda: ( e:=JclExecutor(), hasattr(e,'execute_step'))) do('JEX','DD→環境変数マッピング', lambda: ( e:=JclExecutor(), m:=e._build_env({'TRANSIN':'/data/in.dat','VALIDOUT':'/data/out.dat'}), 'TRANSIN' in m and 'VALIDOUT' in m)) # ── quality モジュール ── from quality.l1_offset_validate import L1OffsetValidator from quality.l2_value_roundtrip import L2RoundtripValidator do('QLT','L1OffsetValidator インスタンス', lambda: ( v:=L1OffsetValidator(), hasattr(v,'validate'))) do('QLT','L2RoundtripValidator インスタンス', lambda: ( v:=L2RoundtripValidator(), hasattr(v,'validate'))) # ── HINA gate: エッジケース ── from hina.gate import check as gate_check, _compute_score do('QG','スコア上限=1.0', lambda: _compute_score({'branch_rate':1.0,'paragraph_rate':1.0},{})<=1.0) do('QG','スコア下限=0.4', lambda: _compute_score({'branch_rate':0.0,'paragraph_rate':0.0},{})>=0.4) do('QG','境界:分岐率0.8999→不合格', lambda: ( r:=gate_check([{'x':1}],{},{'branch_rate':0.8999,'paragraph_rate':1.0,'uncovered_decision_ids':[]}), not r['passed'])) do('QG','境界:分岐率0.9→合格', lambda: ( r:=gate_check([{'x':1}],{},{'branch_rate':0.9,'paragraph_rate':1.0,'uncovered_decision_ids':[]}), r['passed'])) do('QG','issue:段落不足のみ', lambda: ( r:=gate_check([{'x':1}],{},{'branch_rate':1.0,'paragraph_rate':0.5,'uncovered_decision_ids':[]}), not r['passed'] and 'paragraph_gaps' in r['issues'])) # ── 集計 ── print(); [print(l) for l in LOG] total=PASS+FAIL print(f'\n{"="*67}') print(f' Gap Coverage Test Results') print(f' Total: {total} | PASS: {PASS} | FAIL: {FAIL} | RATE: {PASS/max(total,1)*100:.1f}%') print(f' Untested modules covered: hina.hina_agent ✅ jcl.parser ✅ jcl.executor ✅') print(f'{"="*67}') sys.exit(0 if FAIL==0 else 1)