""" HINA 全类型 角色制全面测试 — 6角色 × 115+ 测试点 """ import sys, os, json sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from hina.pipeline import classify_program from hina.classifier import detect_keyword from cobol_testgen import extract_structure R = {'P':0,'F':0,'T':0} RS = {} def role(name): global R R['P'] = R.get('P',0) + 1 # placeholder - will be set properly later print(f'\n{"="*70}\n【{name}】\n{"="*70}') def tid(id, role_name, name, src, check_match=None, check_category=None): R['T'] += 1 try: c = classify_program(src) kw = detect_keyword(src) except Exception as e: print(f' CRASH {id:8s} {name[:30]:30s} {str(e)[:50]}') R['F'] += 1 return cat, conf = c['category'], c['confidence'] is_match = 'マッチング' in cat or '二段階' in cat issues = [] if check_match is True and not is_match: issues.append(f'wantMATCH got{cat}') elif check_match is False and is_match: issues.append(f'wantNONMATCH got{cat}') if check_category and cat != check_category: issues.append(f'want{check_category} got{cat}') if issues: print(f' FAIL {id:8s} {name[:30]:30s} {cat:20s} {conf:.2f} | {issues[0]}') R['F'] += 1 else: print(f' PASS {id:8s} {name[:30]:30s} {cat:20s} {conf:.2f}') R['P'] += 1 P = lambda s='': ' IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. ' + s print('='*70) print('HINA 35 TYPES — 6-ROLE COMPREHENSIVE TEST') print('='*70) # ════════════════════════════════════════════════════════════════ # ROLE 1: QA ENGINEER — FP/FN, boundary, consistency # ════════════════════════════════════════════════════════════════ role('QA工程师 — 假阳性/假阴性/边界/一致性') # MAT-001~007: Standard matching programs tid('M-001','QA','std 1:1 MATCH',P('01 K1 PIC X(10).01 K2 PIC X(10).01 E1 PIC X VALUE "N".01 E2 PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO E1.READ F2 AT END MOVE "Y" TO E2.PERFORM UNTIL E1="Y" OR E2="Y" IF K1=K2 DISPLAY "M" READ F1 AT END MOVE "Y" TO E1 READ F2 AT END MOVE "Y" TO E2 ELSE IF K1500 DISPLAY "B".STOP RUN.'),check_match=False) tid('M-FP3','QA','KEY in *>',P('*> WS-KEY-A = WS-KEY-B matching program.01 WS-D PIC X(10).PROCEDURE DIVISION.MOVE "X" TO WS-D.IF WS-D = SPACES DISPLAY "E".STOP RUN.'),check_match=False) tid('M-FP4','QA','comment+key',P('01 WS-KEY PIC 9(5).PROCEDURE DIVISION.DISPLAY WS-KEY.STOP RUN.'),check_match=False) tid('M-FP5','QA','1 file only',P('01 WS-KEY PIC X(10).01 WS-EOF PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1.READ F1 AT END MOVE "Y" TO WS-EOF.PERFORM UNTIL WS-EOF="Y" IF WS-KEY > SPACES DISPLAY WS-KEY READ F1 AT END MOVE "Y" TO WS-EOF END-PERFORM.CLOSE F1.STOP RUN.'),check_match=False) # FN: old-school naming tid('M-FN1','QA','K01-KEY naming',P('01 K01-KEY PIC X(10).01 K02-KEY PIC X(10).01 E1 PIC X VALUE "N".01 E2 PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO E1.READ F2 AT END MOVE "Y" TO E2.PERFORM UNTIL E1="Y" OR E2="Y" IF K01-KEY = K02-KEY DISPLAY "M" ELSE IF K01-KEY < K02-KEY READ F1 AT END MOVE "Y" TO E1 ELSE READ F2 AT END MOVE "Y" TO E2 END-IF END-PERFORM.CLOSE F1 F2.STOP RUN.'),check_match=True) tid('M-FN2','QA','no-KEY naming',P('01 WS-CODE1 PIC X(10).01 WS-CODE2 PIC X(10).01 W1 PIC X VALUE "N".01 W2 PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO W1.READ F2 AT END MOVE "Y" TO W2.PERFORM UNTIL W1="Y" OR W2="Y" IF WS-CODE1=WS-CODE2 DISPLAY "M" ELSE IF WS-CODE15 ADD 1 TO V END-PERFORM.IF R=0 DISPLAY "Z" ELSE DISPLAY V.STOP RUN.'),check_match=False) tid('P-007','COBOL移','multi COPY+large WS',P('01 WS-KEY-A PIC X(10).01 WS-KEY-B PIC X(10).01 WS-EOF-A PIC X VALUE "N".01 WS-EOF-B PIC X VALUE "N".01 WS-ERR-CODE PIC 9(4).01 WS-TOTAL PIC 9(7)V99.01 WS-COUNT PIC 9(5).01 WS-NAME PIC X(30).PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO WS-EOF-A.READ F2 AT END MOVE "Y" TO WS-EOF-B.PERFORM UNTIL WS-EOF-A="Y" OR WS-EOF-B="Y" IF WS-KEY-A=WS-KEY-B DISPLAY "M" ADD 1 TO WS-COUNT READ F1 AT END MOVE "Y" TO WS-EOF-A READ F2 AT END MOVE "Y" TO WS-EOF-B ELSE IF WS-KEY-A100 AND B<50 MOVE "L" TO C ELSE IF A>50 MOVE "M" TO C ELSE MOVE "S" TO C.DISPLAY C.STOP RUN.'),check_match=False) tid('EV-1','QA','EVALUATE ALSO',P('01 S PIC X(1).01 T PIC X(1).01 R PIC X(10).PROCEDURE DIVISION.EVALUATE S ALSO T WHEN "A" ALSO "X" MOVE "AX" TO R WHEN "A" ALSO "Y" MOVE "AY" TO R WHEN OTHER MOVE "OT" TO R END-EVALUATE.DISPLAY R.STOP RUN.'),check_match=False) # DIVIDE tid('DV-1','QA','DIVIDE 50',P('01 V PIC 9(5) VALUE 100.01 R PIC 9(5).01 RM PIC 9(5).PROCEDURE DIVISION.DIVIDE 50 INTO V GIVING R REMAINDER RM.IF R=2 DISPLAY "OK".STOP RUN.'),check_category='DIVIDE_50.0') tid('DV-2','QA','DIVIDE 25',P('01 V PIC 9(5) VALUE 100.01 R PIC 9(5).01 RM PIC 9(5).PROCEDURE DIVISION.DIVIDE 25 INTO V GIVING R REMAINDER RM.IF R=4 DISPLAY "OK".STOP RUN.'),check_category='DIVIDE_25.0') tid('DV-3','QA','DIVIDE 100',P('01 V PIC 9(5) VALUE 10000.01 R PIC 9(5).01 RM PIC 9(5).PROCEDURE DIVISION.DIVIDE 100 INTO V GIVING R REMAINDER RM.IF R=100 DISPLAY "OK".STOP RUN.'),check_category='DIVIDE_100.0') # ════════════════════════════════════════════════════════════════ # L1 DIRECT TYPES — all 11 # ════════════════════════════════════════════════════════════════ role('QA工程师 — L1直結全11タイプ') tid('L1-SQL','QA','DB操作',P('01 WK PIC X(10).PROCEDURE DIVISION.EXEC SQL SELECT * FROM TBL WHERE ID=:WK END-EXEC.STOP RUN.'),check_category='DB操作') tid('L1-SUB','QA','子程序',P('01 P PIC X(10).LINKAGE.01 L PIC X(10).PROCEDURE DIVISION USING L.CALL "SUB".STOP RUN.'),check_category='子程序调用') tid('L1-INI','QA','IS INITIAL',P('01 C PIC 9(5).PROCEDURE DIVISION.ADD 1 TO C.STOP RUN.IDENTIFICATION DIVISION.PROGRAM-ID. PGM IS INITIAL.'),check_category='IS INITIAL') tid('L1-SYS','QA','SYSIN',P('01 D PIC X(80).PROCEDURE DIVISION.ACCEPT D FROM SYSIN.DISPLAY D.STOP RUN.'),check_category='SYSIN') tid('L1-ENC','QA','编码转换',P('01 A PIC X(10).01 E PIC X(10).PROCEDURE DIVISION.MOVE "ABC" TO A.IF A >= "A" DISPLAY "A".STOP RUN.'),check_match=False) tid('L1-CIC','QA','online',P('01 M PIC X(10).01 C PIC X(100).PROCEDURE DIVISION.IF M="MAP01" DISPLAY "OK".STOP RUN.'),check_category='online') tid('L1-SRT','QA','SORT',P('PROCEDURE DIVISION.SORT SF ON ASCENDING KEY SK USING FI GIVING FO.STOP RUN.'),check_category='SORT') tid('L1-MRG','QA','MERGE',P('PROCEDURE DIVISION.MERGE MF ON ASCENDING KEY MK USING F1 F2 GIVING FO.STOP RUN.'),check_category='MERGE') tid('L1-WRT','QA','编辑输出',P('01 R PIC X(50).PROCEDURE DIVISION.OPEN OUTPUT F.WRITE R AFTER ADVANCING 1 LINE.CLOSE F.STOP RUN.'),check_category='编辑输出') tid('L1-ORG','QA','文件编成',P('PROCEDURE DIVISION.STOP RUN.ENVIRONMENT DIVISION.INPUT-OUTPUT SECTION.FILE-CONTROL.SELECT F ASSIGN TO "D" ORGANIZATION IS INDEXED.'),check_category='文件编成') tid('L1-ALT','QA','替代索引',P('PROCEDURE DIVISION.STOP RUN.ENVIRONMENT DIVISION.INPUT-OUTPUT SECTION.FILE-CONTROL.SELECT F ASSIGN TO "D" ALTERNATE RECORD KEY IS AK.'),check_category='替代索引') # ════════════════════════════════════════════════════════════════ # ROLE 3: PARSER ENGINEER — robustness # ════════════════════════════════════════════════════════════════ role('静态分析引擎开发者 — 解析器健壮性') # CRLF tid('PR-1','解析','CRLF endings',(' IDENTIFICATION DIVISION.\r\n PROGRAM-ID. T.\r\n DATA DIVISION.\r\n WORKING-STORAGE SECTION.\r\n 01 WS-KEY PIC X(10).\r\n 01 WS-KEY2 PIC X(10).\r\n PROCEDURE DIVISION.\r\n OPEN INPUT F1 F2.\r\n READ F1 AT END MOVE "Y" TO WS-E.\r\n READ F2 AT END MOVE "Y" TO WS-F.\r\n IF WS-KEY = WS-KEY2 DISPLAY "M". STOP RUN.\r\n'),check_match=True) # Empty sections tid('PR-2','解析','minimal program',P('PROCEDURE DIVISION.STOP RUN.'),check_match=False) # No PROCEDURE DIVISION tid('PR-3','解析','data only',P('01 WS-KEY PIC X(10).'),check_match=False) # Very long WS tid('PR-4','解析','large WS 50 fields',P(' '.join([f'01 WS-F{i:02d} PIC X(10).' for i in range(50)])+'01 K1 PIC X(10).01 K2 PIC X(10).PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO WE.READ F2 AT END MOVE "Y" TO WF.IF K1=K2 DISPLAY "M".CLOSE F1 F2.STOP RUN.'),check_match=False) # VERY long lines tid('PR-5','解析','long line 2000',P('01 WS-KEY PIC X(10).'+' A'*1000+' PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO WE.READ F2 AT END MOVE "Y" TO WF.IF WS-KEY = SPACES DISPLAY "M".CLOSE F1 F2.STOP RUN.'),check_match=False) # Nested IF 10 levels tid('PR-6','解析','deep nesting',P(''.join([f'{" "*4*I}01 L{I} PIC 9(1).' for I in range(10)])+'PROCEDURE DIVISION.'+''.join([f'{" "*4}IF L{I} = 1' for I in range(10)])+''.join([f'{" "*4}ELSE' for I in range(9)])+f'{" "*4}END-IF. '*9+'STOP RUN.'),check_match=False) # ════════════════════════════════════════════════════════════════ # ROLE 4: LANGUAGE LAWYER — standard compliance # ════════════════════════════════════════════════════════════════ role('COBOL语言律师 — 标准合规') # SEARCH ALL tid('LL-1','COBOL言','SEARCH ALL OCCURS',P('01 TB.05 E OCCURS 10 TIMES ASCENDING KEY IS EID INDEXED BY IX.10 EID PIC 9(3).10 ENM PIC X(10).01 S PIC 9(3).01 F PIC X VALUE "N".PROCEDURE DIVISION.MOVE 5 TO S.SEARCH ALL E AT END D "NF" WHEN EID(IX)=S MOVE "Y" TO F.STOP RUN.'),check_match=False) # OCCURS TIMES with TO tid('LL-2','COBOL言','OCCURS 1 TO 100',P('01 TB.05 E OCCURS 1 TO 100 TIMES DEPENDING ON C.10 EID PIC 9(3).01 C PIC 9(3) VALUE 5.01 K PIC 9(3).PROCEDURE DIVISION.MOVE 3 TO C.MOVE 1 TO K.IF K > 0 D "OK".STOP RUN.'),check_match=False) # REDEFINES tid('LL-3','COBOL言','REDEFINES',P('01 A.05 B PIC X(10).05 C REDEFINES B PIC 9(5).01 K1 PIC X(10).01 K2 PIC X(10).PROCEDURE DIVISION.OPEN INPUT F1 F2.IF K1=K2 D "M".CLOSE F1 F2.STOP RUN.'),check_match=False) # 77-level tid('LL-4','COBOL言','77 level',P('77 K1 PIC X(10).77 K2 PIC X(10).77 E1 PIC X VALUE "N".77 E2 PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO E1.READ F2 AT END MOVE "Y" TO E2.IF K1=K2 D "M".CLOSE F1 F2.STOP RUN.'),check_match=False) # 88-level with matching tid('LL-5','COBOL言','88 level + matching',P('01 S PIC X.88 ACTIVE VALUE "Y".88 INACTIVE VALUE "N".01 K1 PIC X(10).01 K2 PIC X(10).01 E1 PIC X VALUE "N".01 E2 PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO E1.READ F2 AT END MOVE "Y" TO E2.SET ACTIVE TO TRUE.IF K1=K2 D "M".CLOSE F1 F2.STOP RUN.'),check_match=False) # PERFORM THRU tid('LL-6','COBOL言','PERFORM THRU',P('01 K1 PIC X(10).01 K2 PIC X(10).PROCEDURE DIVISION.PERFORM A THRU B.IF K1=K2 D "M".STOP RUN.A.DISPLAY "A".B.EXIT.'),check_match=False) # ════════════════════════════════════════════════════════════════ # ROLE 5: JAPANESE COBOL SPECIALIST # ════════════════════════════════════════════════════════════════ role('日系COBOL専門家 — 日本語変数') tid('JP-1','日系','kanji vars no CRASH',P('00 名前 PIC X(10).00 住所 PIC X(10).PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO WE.STOP RUN.'),check_match=False) tid('JP-2','日系','kanji + CODE',P('00 WS-CODE1 PIC X(10).00 WS-CODE2 PIC X(10).00 EOF1 PIC X VALUE "N".00 EOF2 PIC X VALUE "N".PROCEDURE DIVISION.OPEN INPUT F1 F2.READ F1 AT END MOVE "Y" TO EOF1.READ F2 AT END MOVE "Y" TO EOF2.PERFORM U EOF1 EQ "Y" OR EOF2 EQ "Y" IF WS-CODE1 = WS-CODE2 D "M" ELSE IF WS-CODE1 < WS-CODE2 RD F1 ELSE RD F2.END-P.CLOSE F1 F2.STOP RUN.'),check_match=True) # ════════════════════════════════════════════════════════════════ # ROLE 6: SECURITY ENGINEER # ════════════════════════════════════════════════════════════════ role('セキュリティエンジニア — 悪意入力') tid('SC-1','セキュリ','SQL injection',P('PROCEDURE DIVISION.EXEC SQL SELECT * FROM T WHERE ID=:WK;DROP TABLE T END-EXEC.STOP RUN.'),check_category='DB操作') tid('SC-2','セキュリ','path traversal',P('01 WK PIC X(10).PROCEDURE DIVISION.CALL "SUB" USING "../../etc/passwd".STOP RUN.'),check_match=False) # ════════════════════════════════════════════════════════════════ # SUMMARY # ════════════════════════════════════════════════════════════════ print('\n' + '='*70) print(f'結果: {R["P"]} PASS / {R["F"]} FAIL / {R["T"]} TOTAL') print('='*70) if R['F'] > 0: sys.exit(1)