""" HINA 全35类型 高密度测试 — 每种类型 5+ 变体 包括: 正常形 / 別スタイル / 最小形 / 境界形 / FP攻撃 / FN攻撃 / 命名バリエーション """ import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) from hina.pipeline import classify_program from hina.classifier import detect_keyword STATS = {"pass": 0, "fail": 0, "total": 0, "by_type": {}} def test(hina_id, variant, name, src, check_matching=None, check_category=None): STATS["total"] += 1 STATS["by_type"].setdefault(hina_id, {"pass": 0, "fail": 0, "total": 0}) STATS["by_type"][hina_id]["total"] += 1 try: c = classify_program(src) kw = detect_keyword(src) except Exception as e: print(f'CRASH {hina_id}/{variant} {name[:25]:25s} {str(e)[:50]}') STATS["fail"] += 1 STATS["by_type"][hina_id]["fail"] += 1 return cat = c['category'] conf = c['confidence'] is_match = 'マッチング' in cat or '二段階' in cat issues = [] if check_matching is True and not is_match: issues.append(f'want MATCH got {cat}') elif check_matching is False and is_match: issues.append(f'want NON-MATCH got {cat}') if check_category and cat != check_category: issues.append(f'want {check_category} got {cat}') if issues: print(f'FAIL {hina_id}/{variant} {name[:25]:25s} {cat:20s} {conf:.2f} | {issues[0]}') STATS["fail"] += 1 STATS["by_type"][hina_id]["fail"] += 1 else: STATS["pass"] += 1 STATS["by_type"][hina_id]["pass"] += 1 P = ' IDENTIFICATION DIVISION. PROGRAM-ID. T. DATA DIVISION. WORKING-STORAGE SECTION. ' print('='*95) print('HINA 35 TYPES HIGH-DENSITY TEST') print('='*95) # ════════════════════════════════════ # MATCHING SERIES # ════════════════════════════════════ print('\n--- MATCHING ---') test('M','1to1','std WS-KEY',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'. 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' READ F1 AT END MOVE 'Y' TO WS-EOF-A READ F2 AT END MOVE 'Y' TO WS-EOF-B ELSE IF WS-KEY-A 500 DISPLAY 'BIG' ELSE DISPLAY 'SMALL'. STOP RUN.''',check_matching=False) test('M','fp-1file','FP: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 'EMPTY' ELSE DISPLAY WS-KEY READ F1 AT END MOVE 'Y' TO WS-EOF END-PERFORM. CLOSE F1. STOP RUN.''',check_matching=False) test('M','fp-noopen','FP:no FILE at all',P+''' 01 WS-KEY PIC X(10). PROCEDURE DIVISION. MOVE 'KEY' TO WS-KEY. DISPLAY WS-KEY. STOP RUN.''',check_matching=False) test('M','fp-nokey','FP:no KEY var',P+''' 01 WS-EOF PIC X VALUE 'N'. 01 WS-TOTAL PIC 9(5). PROCEDURE DIVISION. OPEN INPUT F1. READ F1 AT END MOVE 'Y' TO WS-EOF. PERFORM UNTIL WS-EOF='Y' ADD 1 TO WS-TOTAL READ F1 AT END MOVE 'Y' TO WS-EOF END-PERFORM. CLOSE F1. STOP RUN.''',check_matching=False) test('M','fn-prevkey','WS-PREV-KEY valid',P+''' 01 WS-KEY PIC X(10). 01 WS-PREV-KEY PIC X(10) VALUE SPACES. 01 WS-EOF PIC X VALUE 'N'. 01 WS-DC PIC 9(4). PROCEDURE DIVISION. OPEN INPUT F. READ F AT END MOVE 'Y' TO WS-EOF. PERFORM UNTIL WS-EOF='Y' IF WS-KEY=WS-PREV-KEY ADD 1 TO WS-DC ELSE MOVE WS-KEY TO WS-PREV-KEY READ F AT END MOVE 'Y' TO WS-EOF END-PERFORM. CLOSE F. STOP RUN.''',check_category='項目チェック(重複含む)') # ════════════════════════════════════ # KEY BREAK series # ════════════════════════════════════ print('\n--- KEY BREAK ---') test('KB','ws-prev-key','WS-PREV-KEY+ACCUM',P+''' 01 WS-PREV-KEY PIC X(10). 01 WS-KEY PIC X(10). 01 WS-SUM PIC 9(7)V99. 01 WS-EOF PIC X VALUE 'N'. PROCEDURE DIVISION. OPEN INPUT F. READ F AT END MOVE 'Y' TO WS-EOF. PERFORM UNTIL WS-EOF='Y' IF WS-KEY NOT = WS-PREV-KEY IF WS-PREV-KEY NOT = SPACES DISPLAY WS-PREV-KEY WS-SUM MOVE WS-KEY TO WS-PREV-KEY MOVE 0 TO WS-SUM ADD 1 TO WS-SUM READ F AT END MOVE 'Y' TO WS-EOF END-PERFORM. CLOSE F. STOP RUN.''',check_category='項目チェック(重複含む)') test('KB','fp-only-cnt','FP:CNT no match',P+''' 01 WS-ERR-PIC PIC X(10). 01 WS-CNT PIC 9(5). PROCEDURE DIVISION. MOVE 'ABC' TO WS-ERR-PIC. DISPLAY WS-ERR-PIC. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # CONDITION BRANCH series # ════════════════════════════════════ print('\n--- IF/EVALUATE ---') test('IF','normal','IF-ELSE',P+''' 01 A PIC 9(5). 01 B PIC 9(5). 01 C PIC X(10). PROCEDURE DIVISION. IF A > 100 AND B < 50 MOVE 'LARGE' TO C ELSE IF A > 50 MOVE 'MEDIUM' TO C ELSE MOVE 'SMALL' TO C. DISPLAY C. STOP RUN.''',check_matching=False) test('IF','not-eq','NOT =',P+''' 01 A PIC 9(5). 01 B PIC 9(5). PROCEDURE DIVISION. IF A NOT = B DISPLAY 'DIFF' ELSE DISPLAY 'SAME'. STOP RUN.''',check_matching=False) test('EV','normal','EVALUATE',P+''' 01 S PIC X(1). 01 R PIC X(10). PROCEDURE DIVISION. EVALUATE S WHEN 'A' MOVE 'ACTIVE' TO R WHEN 'I' MOVE 'INACTIVE' TO R WHEN OTHER MOVE 'UNKNOWN' TO R END-EVALUATE. DISPLAY R. STOP RUN.''',check_matching=False) test('EV','also','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 'A-X' TO R WHEN 'A' ALSO 'Y' MOVE 'A-Y' TO R WHEN OTHER MOVE 'OTHER' TO R END-EVALUATE. DISPLAY R. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # DIVIDE series # ════════════════════════════════════ print('\n--- DIVIDE ---') test('DV','50','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') test('DV','25','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') test('DV','100','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') test('DV','fp-var','FP:var name 50',P+''' 01 WS-50 PIC 9(5). 01 V PIC 9(5) VALUE 100. PROCEDURE DIVISION. MOVE 30 TO WS-50. DIVIDE WS-50 INTO V. STOP RUN.''',check_matching=False) test('DV','fp-mul','FP:MULTIPLY',P+''' 01 A PIC 9(5) VALUE 50. PROCEDURE DIVISION. MULTIPLY 3 BY A. IF A=150 DISPLAY 'OK'. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # CICS online # ════════════════════════════════════ print('\n--- CICS ---') test('CICS','map','MAP var',P+''' 01 WS-MAP PIC X(10). 01 WS-CA PIC X(100). PROCEDURE DIVISION. IF WS-MAP = 'MAP01' DISPLAY 'OK'. STOP RUN.''',check_category='online') test('CICS','dfh','DFHCOMMAREA',P+''' 01 WS-CA PIC X(100). 01 WS-RESP PIC S9(8) COMP. PROCEDURE DIVISION. *> EXEC CICS LINK PROGRAM('PGM1') COMMAREA(WS-CA) RESP(WS-RESP) END-EXEC. IF WS-RESP = 0 DISPLAY 'OK'. STOP RUN.''',check_matching=False) # comment stripped test('CICS','fp-no','FP:no keyword',P+''' 01 WS-DATA PIC X(100). PROCEDURE DIVISION. MOVE 'CICS' TO WS-DATA. DISPLAY WS-DATA. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # SEARCH ALL # ════════════════════════════════════ print('\n--- SEARCH ---') test('SR','all','SEARCH ALL',P+''' 01 TBL. 05 E OCCURS 10 TIMES ASCENDING KEY IS EID INDEXED BY IX. 10 EID PIC 9(03). 10 ENM PIC X(10). 01 S PIC 9(03). 01 F PIC X VALUE 'N'. PROCEDURE DIVISION. MOVE 5 TO S. SEARCH ALL E AT END DISPLAY 'NOT FOUND' WHEN EID(IX)=S MOVE 'Y' TO F. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # SORT/MERGE # ════════════════════════════════════ print('\n--- SORT/MERGE ---') test('SRT','asc','SORT ASC',P+''' 01 WS-DATA PIC X(80). PROCEDURE DIVISION. SORT SF ON ASCENDING KEY SK USING F1 GIVING FO. STOP RUN.''',check_category='SORT') test('SRT','desc','SORT DESC',P+''' 01 WS-DATA PIC X(80). PROCEDURE DIVISION. SORT SF ON DESCENDING KEY SK USING F1 GIVING FO. STOP RUN.''',check_category='SORT') test('SRT','multi','SORT multi-key',P+''' 01 WS-DATA PIC X(80). PROCEDURE DIVISION. SORT SF ON ASCENDING KEY K1 K2 USING F1 GIVING FO. STOP RUN.''',check_category='SORT') test('MRG','normal','MERGE',P+''' 01 WS-DATA PIC X(80). PROCEDURE DIVISION. MERGE MF ON ASCENDING KEY MK USING F1 F2 GIVING FO. STOP RUN.''',check_category='MERGE') test('SRT','fp','FP:no SORT',P+''' 01 WS-DATA PIC X(80). PROCEDURE DIVISION. MOVE 'SORT KEY' TO WS-DATA. DISPLAY WS-DATA. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # L1 DIRECT TYPES # ════════════════════════════════════ print('\n--- L1 DIRECT ---') test('L1','sql','EXEC SQL',P+''' 01 WS-ID PIC X(10). PROCEDURE DIVISION. EXEC SQL SELECT * FROM TBL WHERE ID=:WS-ID END-EXEC. STOP RUN.''',check_category='DB操作') test('L1','sql-cmt','*>EXEC SQL comment',P+''' 01 WS-DATA PIC X(10). PROCEDURE DIVISION. *> EXEC SQL SELECT * FROM TBL END-EXEC. MOVE 'X' TO WS-DATA. STOP RUN.''',check_matching=False) test('L1','sql-literal','FP:SQL in literal',P+''' 01 WS-MSG PIC X(50). PROCEDURE DIVISION. MOVE 'EXEC SQL SELECT * FROM TBL' TO WS-MSG. STOP RUN.''',check_matching=False) test('L1','call','CALL+LINKAGE',P+''' 01 WS-P PIC X(10). LINKAGE SECTION. 01 LS-P PIC X(10). PROCEDURE DIVISION USING LS-P. CALL 'SUB' USING WS-P. STOP RUN.''',check_category='子程序调用') test('L1','call-only','FP:CALL no LINKAGE',P+''' 01 WS-P PIC 9(5). PROCEDURE DIVISION. CALL 'SUB' USING WS-P. STOP RUN.''',check_matching=False) test('L1','link-only','FP:LINKAGE no CALL',P+''' 01 WS-X PIC 9(5). LINKAGE SECTION. 01 LS-P PIC X(10). PROCEDURE DIVISION USING LS-P. MOVE 'X' TO LS-P. GOBACK.''',check_matching=False) test('L1','init','IS INITIAL',P+'''01 C PIC 9(5) VALUE 0. PROCEDURE DIVISION. ADD 1 TO C. DISPLAY C. STOP RUN. IDENTIFICATION DIVISION. PROGRAM-ID. PGM IS INITIAL.''',check_category='IS INITIAL') test('L1','sys','SYSIN',P+'''01 D PIC X(80). PROCEDURE DIVISION. ACCEPT D FROM SYSIN. DISPLAY D. STOP RUN.''',check_category='SYSIN') test('L1','sys-var','FP:SYSIN variable',P+'''01 SYSIN PIC X(80). PROCEDURE DIVISION. MOVE 'DATA' TO SYSIN. DISPLAY SYSIN. STOP RUN.''',check_matching=False) test('L1','enc','encoding',P+'''01 A PIC X(10) VALUE 'ABCDEF'. 01 E PIC X(10). PROCEDURE DIVISION. MOVE 'ABC' TO A. DISPLAY A. STOP RUN.''',check_matching=False) test('L1','wrt-after','WRITE AFTER',P+'''01 R PIC X(50). PROCEDURE DIVISION. OPEN OUTPUT F. WRITE R AFTER ADVANCING 1 LINE. CLOSE F. STOP RUN.''',check_category='编辑输出') test('L1','wrt-before','WRITE BEFORE',P+'''01 R PIC X(50). PROCEDURE DIVISION. OPEN OUTPUT F. WRITE R BEFORE ADVANCING 2 LINES. CLOSE F. STOP RUN.''',check_category='编辑输出') test('L1','org','ORGANIZATION IS',P+'''PROCEDURE DIVISION. STOP RUN. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT F ASSIGN TO 'F.DAT' ORGANIZATION IS INDEXED.''',check_category='文件编成') test('L1','alt','ALTERNATE KEY',P+'''PROCEDURE DIVISION. STOP RUN. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT F ASSIGN TO 'F.DAT' ALTERNATE RECORD KEY IS AK.''',check_category='替代索引') test('L1','alt-org','ALT+ORG conflict',P+'''PROCEDURE DIVISION. STOP RUN. ENVIRONMENT DIVISION. INPUT-OUTPUT SECTION. FILE-CONTROL. SELECT F ASSIGN TO 'F.DAT' ORGANIZATION IS INDEXED RECORD KEY IS RK ALTERNATE RECORD KEY IS AK.''',check_category='替代索引') # ════════════════════════════════════ # CSV series # ════════════════════════════════════ print('\n--- CSV ---') test('CSV','merge','CSV merge',P+''' 01 F1 PIC X(10) VALUE 'A'. 01 F2 PIC X(10) VALUE 'B'. 01 C PIC X(50). 01 P PIC 9(3) VALUE 1. PROCEDURE DIVISION. STRING F1 DELIMITED BY SPACES ',' DELIMITED BY SIZE F2 DELIMITED BY SPACES INTO C WITH POINTER P. DISPLAY C. STOP RUN.''',check_category='CSV合并') test('CSV','split','CSV split',P+''' 01 L PIC X(50) VALUE 'A,B,C'. 01 C PIC 9(3). PROCEDURE DIVISION. INSPECT L TALLYING C FOR ALL ','. INSPECT L REPLACING ALL ',' BY '|'. DISPLAY L. STOP RUN.''',check_category='CSV拆分') test('CSV','fp-str','FP:STRING no CSV',P+''' 01 A PIC X(5) VALUE 'HELLO'. 01 B PIC X(5) VALUE 'WORLD'. 01 R PIC X(50). 01 P PIC 9(3) VALUE 1. PROCEDURE DIVISION. STRING A DELIMITED BY SPACES ' ' DELIMITED BY SIZE B DELIMITED BY SPACES INTO R WITH POINTER P. STOP RUN.''',check_matching=False) test('CSV','fp-insp','FP:INSPECT no CSV',P+''' 01 T PIC X(30) VALUE 'AAABBB'. 01 C PIC 9(3). PROCEDURE DIVISION. INSPECT T TALLYING C FOR ALL 'A'. DISPLAY C. STOP RUN.''',check_matching=False) # ════════════════════════════════════ # EDIT PROCESSING # ════════════════════════════════════ print('\n--- EDIT ---') test('EDIT','ws-err','WS-ERR field',P+''' 01 WS-ERR-CODE PIC 9(4). 01 WS-V PIC 9(5). PROCEDURE DIVISION. IF WS-V = 0 MOVE 9999 TO WS-ERR-CODE ELSE DISPLAY 'OK'. STOP RUN.''',check_category='編集処理(校验)') test('EDIT','fp','FP:no ERR',P+''' 01 WS-V PIC 9(5). PROCEDURE DIVISION. MOVE 1 TO WS-V. DISPLAY WS-V. STOP RUN.''',check_matching=False) print('\n'+'='*95) print(f'RESULT: {STATS["pass"]} PASS / {STATS["fail"]} FAIL / {STATS["total"]} TOTAL') print('='*95) if STATS["fail"] > 0: for tid, s in sorted(STATS["by_type"].items()): print(f' {tid}: {s["pass"]}/{s["total"]} ({s["pass"]/max(s["total"],1)*100:.0f}%)') sys.exit(1)