use super::*;
fn cobol(lines: &[&str]) -> String {
lines
.iter()
.map(|l| {
assert!(l.len() <= 65, "{l:?} runs past column 72");
format!(" {l}\n")
})
.collect()
}
fn ended(source: &str) -> (String, String, String) {
let walker = Harness::source(source).run(Executor::Interpreter);
let vm = Harness::source(source).run(Executor::Vm);
assert_eq!((&vm.out, &vm.ending), (&walker.out, &walker.ending), "{}", vm.err);
let abend = walker.ending.unwrap_err();
(abend.code.to_string(), abend.message, walker.out)
}
fn sorting(input_procedure: &str) -> String {
cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. SORTER.",
"ENVIRONMENT DIVISION.",
"INPUT-OUTPUT SECTION.",
"FILE-CONTROL.",
" SELECT S-FILE ASSIGN TO SORTWK.",
"DATA DIVISION.",
"FILE SECTION.",
"SD S-FILE.",
"01 S-REC PIC X(4).",
"PROCEDURE DIVISION.",
"MAIN-P.",
" SORT S-FILE ON ASCENDING KEY S-REC",
" INPUT PROCEDURE IN-P OUTPUT PROCEDURE OUT-P",
" DISPLAY 'AFTER'",
" GOBACK.",
"IN-P.",
input_procedure,
"OUT-P.",
" CONTINUE.",
])
}
#[test]
fn a_stop_run_or_goback_in_a_sort_procedure_ends_the_run_with_igz0012s() {
for (statement, how) in [(" STOP RUN.", "STOP RUN"), (" GOBACK.", "GOBACK or EXIT PROGRAM")] {
let (code, message, out) = ended(&sorting(statement));
assert_eq!((code.as_str(), out.as_str()), ("U4038", ""), "{statement}");
assert_eq!(message, format!("IGZ0012S There was an invalid attempt to end a sort or merge. ({how} in a procedure of SORT S-FILE)"));
}
}
#[test]
fn a_goback_in_an_xml_processing_procedure_ends_the_run_with_igz0227s() {
let source = |procedure: &str| {
cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. PARSER.",
"DATA DIVISION.",
"WORKING-STORAGE SECTION.",
"01 DOC PIC X(8) VALUE '<a>x</a>'.",
"PROCEDURE DIVISION.",
"MAIN-P.",
" XML PARSE DOC PROCESSING PROCEDURE P",
" DISPLAY 'AFTER'",
" GOBACK.",
"P.",
procedure,
])
};
let (code, message, out) = ended(&source(" GOBACK."));
assert_eq!((code.as_str(), out.as_str()), ("U4038", ""));
assert!(message.starts_with("IGZ0227S There was an invalid attempt to end an XML PARSE statement."), "{message}");
let stopped = Harness::source(&source(" STOP RUN.")).run(Executor::Interpreter);
assert_eq!(stopped.ending, Ok(Ending::StopRun), "STOP RUN may end a processing procedure");
}
#[test]
fn a_cancel_of_an_active_program_ends_the_run_with_igz0032s() {
let source = cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. MAIN.",
"DATA DIVISION.",
"WORKING-STORAGE SECTION.",
"01 WS-A PIC X(8) VALUE 'A'.",
"PROCEDURE DIVISION.",
" CALL WS-A",
" GOBACK.",
"END PROGRAM MAIN.",
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. A.",
"PROCEDURE DIVISION.",
" CALL 'B'",
" GOBACK.",
"END PROGRAM A.",
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. B.",
"PROCEDURE DIVISION.",
" CANCEL 'A'",
" GOBACK.",
"END PROGRAM B.",
]);
let (code, message, _) = ended(&source);
assert_eq!((code.as_str(), message.as_str()), ("U4038", "IGZ0032S A CANCEL was attempted on active program A."));
}
#[test]
fn a_main_program_past_its_last_line_ends_with_igz0037s_and_a_called_one_returns() {
let source = cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. MAIN.",
"PROCEDURE DIVISION.",
" CALL 'SUB'",
" DISPLAY 'BACK IN MAIN'.",
"END PROGRAM MAIN.",
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. SUB.",
"PROCEDURE DIVISION.",
" DISPLAY 'IN SUB'.",
"END PROGRAM SUB.",
]);
let (code, message, out) = ended(&source);
assert_eq!(out, "IN SUB\nBACK IN MAIN\n");
assert_eq!((code.as_str(), message.as_str()), ("U4038", "IGZ0037S The flow of control in program MAIN proceeded beyond the last line of the program."));
}
#[test]
fn igz0037s_is_placed_at_the_last_paragraph() {
let source = cobol(&[
"IDENTIFICATION DIVISION.",
"PROGRAM-ID. MAIN.",
"PROCEDURE DIVISION.",
"FIRST-P.",
" DISPLAY 'ONE'.",
"LAST-P.",
" DISPLAY 'TWO'.",
]);
let (code, _, out) = ended(&source);
let abend = Harness::source(&source).run(Executor::Vm).ending.unwrap_err();
assert_eq!((code.as_str(), out.as_str(), abend.pos.line), ("U4038", "ONE\nTWO\n", 6));
}