diff --git a/.gitignore b/.gitignore index 6485b6c5..5974a0a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ *~ *.lock *.DS_Store -*.swp *.out ALLTESTS ALPHAT @@ -51,7 +50,6 @@ hs_err_pid* ############################## ## Maven ############################## -target/ pom.xml.tag pom.xml.releaseBackup pom.xml.versionsBackup diff --git a/CC##99 b/CC##99 deleted file mode 100755 index cbb035ae..00000000 Binary files a/CC##99 and /dev/null differ diff --git a/CC##TEST b/CC##TEST deleted file mode 100755 index d950f7d3..00000000 Binary files a/CC##TEST and /dev/null differ diff --git a/approvaltest b/approvaltest old mode 100755 new mode 100644 index 31b05276..56269004 --- a/approvaltest +++ b/approvaltest @@ -1,6 +1,6 @@ -./cobolcheck -p NUMBERS > approval-test-actual.txt -./cobolcheck -p ALPHA >> approval-test-actual.txt -./cobolcheck -p GREETING >> approval-test-actual.txt -./cobolcheck -p FILECOPY >> approval-test-actual.txt -./cobolcheck -p MOCKTEST >> approval-test-actual.txt -./cobolcheck -p DPICNUMBERS >> approval-test-actual.txt +./temp/approvalTest/cobolcheck -p NUMBERS > ./actual-output.txt +./temp/approvalTest/cobolcheck -p ALPHA >> ./actual-output.txt +./temp/approvalTest/cobolcheck -p GREETING >> ./actual-output.txt +./temp/approvalTest/cobolcheck -p FILECOPY >> ./actual-output.txt +./temp/approvalTest/cobolcheck -p MOCKTEST >> ./actual-output.txt +./temp/approvalTest/cobolcheck -p DPICNUMBERS >> ./actual-output.txt \ No newline at end of file diff --git a/approvaltestWin.cmd b/approvaltestWin.cmd index 0a829e35..475f021f 100644 --- a/approvaltestWin.cmd +++ b/approvaltestWin.cmd @@ -1 +1 @@ -./cobolcheck -p ALPHA DB2PROG DPICNUMBERS FILECOPY GREETING MOCK MOCKPARA MOCKTEST NUMBERS RETURNCODE TESTNESTED LONGLINESANDNUMBERS > approval-test-actual.txt \ No newline at end of file +temp/approvalTest/cobolcheck.cmd -p NUMBERS ALPHA GREETING FILECOPY MOCKTEST DPICNUMBERS > actual-output.txt \ No newline at end of file diff --git a/build.gradle b/build.gradle index aef457b5..1e7ef183 100644 --- a/build.gradle +++ b/build.gradle @@ -11,8 +11,8 @@ def productName = 'cobol-check' group = 'org.openmainframeproject' description = 'Unit testing framework for Cobol' -def approvalExpectedOutput = "approval-test-expected.txt" -def approvalActualOutput = "output/testResults.txt" +def approvalExpectedOutput = "./expected-output.txt" +def approvalActualOutput = "./actual-output.txt" sonarqube { properties { @@ -162,12 +162,17 @@ task copyJarToExtension(type: Copy) { task copyRunScripts(type: Copy) { description 'Makes copies of run scripts' + + // copy and modify run scripts for bin version from "${projectDir}/cobolcheck.cmd" - into "${projectDir}/GradleTemp" + into "${projectDir}/temp/approvalTest" filter { line -> line.replaceAll('@VERSION@', productVersion) } from "${projectDir}/cobolcheck" - into "${projectDir}/GradleTemp" + into "${projectDir}/temp/approvalTest" filter { line -> line.replaceAll('@VERSION@', productVersion) } + + println("Copied with jar version ${productVersion} to approvalTest directory") + } task prepareDistribution(type: Zip) { @@ -185,37 +190,52 @@ task prepareDistribution(type: Zip) { rename("build/libs/(.*)", "\$1") } - from ("${projectDir}/GradleTemp") + from ("${projectDir}/gradleBuildTemp") doLast { - delete "GradleTemp" + delete "${projectDir}/gradleBuildTemp" } } -def approvalTest = tasks.register("approvalTest", Test) { +def approvalTest +approvalTest = tasks.register("approvalTest", Test) { description 'Run approval test only' - dependsOn fatJar + dependsOn copyJarToBin, copyRunScripts + def output = -1 + def weRanATest = false + def runningOs = OS_NAME.toLowerCase() - if ("${OS_NAME}" == "linux") { + if (runningOs == "linux") { println "Linux detected" + + // grant execute permission to run script + def procChmod = "chmod +x ./approvaltest".execute() + procChmod.waitForProcessOutput(System.out, System.err) + def proc = "./approvaltest".execute() proc.waitForProcessOutput(System.out, System.err) + weRanATest = true } - if ("${OS_NAME}".toLowerCase().contains("windows")) { + if (runningOs.contains("windows")) { println "Windows detected" def proc = "./approvaltestWin.cmd".execute() proc.waitForProcessOutput(System.out, System.err) + weRanATest = true } - output = new BuildHelper().compareFiles(approvalExpectedOutput, approvalActualOutput, true) - println "exit from compare: ${output}" - - if (output != 0) { - println "*** FAIL ***" - throw new StopExecutionException("${approvalExpectedOutput} and ${approvalActualOutput} are different") + if (!weRanATest){ + println "No prepared test for the OS detected: ${runningOs} - skipping" } else { - println "${approvalExpectedOutput} matches ${approvalActualOutput} - PASS" + output = new BuildHelper().compareFiles(approvalExpectedOutput, approvalActualOutput, true) + println "exit from compare: ${output}" + + if (output != 0) { + println "*** FAIL ***" + throw new StopExecutionException("${approvalExpectedOutput} and ${approvalActualOutput} are different") + } else { + println "${approvalExpectedOutput} matches ${approvalActualOutput} - PASS" + } } } @@ -241,7 +261,7 @@ task osInfo { } class BuildHelper{ - int compareFiles(String file1, String file2, boolean trimLines){ + static int compareFiles(String file1, String file2, boolean trimLines){ BufferedReader reader1 BufferedReader reader2 try{ diff --git a/build/distributions/cobol-check-0.2.1.zip b/build/distributions/cobol-check-0.2.1.zip deleted file mode 100644 index 624c7221..00000000 Binary files a/build/distributions/cobol-check-0.2.1.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.10.zip b/build/distributions/cobol-check-0.2.10.zip deleted file mode 100644 index b0e8c4b2..00000000 Binary files a/build/distributions/cobol-check-0.2.10.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.11.zip b/build/distributions/cobol-check-0.2.11.zip deleted file mode 100644 index 7f47d5f6..00000000 Binary files a/build/distributions/cobol-check-0.2.11.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.12.zip b/build/distributions/cobol-check-0.2.12.zip deleted file mode 100644 index 4de28a99..00000000 Binary files a/build/distributions/cobol-check-0.2.12.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.13.zip b/build/distributions/cobol-check-0.2.13.zip deleted file mode 100644 index 608f62fb..00000000 Binary files a/build/distributions/cobol-check-0.2.13.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.14.zip b/build/distributions/cobol-check-0.2.14.zip deleted file mode 100644 index 3616f097..00000000 Binary files a/build/distributions/cobol-check-0.2.14.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.15.zip b/build/distributions/cobol-check-0.2.15.zip deleted file mode 100644 index 5581678b..00000000 Binary files a/build/distributions/cobol-check-0.2.15.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.16.zip b/build/distributions/cobol-check-0.2.16.zip deleted file mode 100644 index d6bf2d30..00000000 Binary files a/build/distributions/cobol-check-0.2.16.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.17.zip b/build/distributions/cobol-check-0.2.17.zip deleted file mode 100644 index 0665270d..00000000 Binary files a/build/distributions/cobol-check-0.2.17.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.18.zip b/build/distributions/cobol-check-0.2.18.zip deleted file mode 100644 index ea5d17cf..00000000 Binary files a/build/distributions/cobol-check-0.2.18.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.2.zip b/build/distributions/cobol-check-0.2.2.zip deleted file mode 100644 index 5b99a4ff..00000000 Binary files a/build/distributions/cobol-check-0.2.2.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.3.zip b/build/distributions/cobol-check-0.2.3.zip deleted file mode 100644 index ff8b3185..00000000 Binary files a/build/distributions/cobol-check-0.2.3.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.4.zip b/build/distributions/cobol-check-0.2.4.zip deleted file mode 100644 index f8693aa8..00000000 Binary files a/build/distributions/cobol-check-0.2.4.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.5.zip b/build/distributions/cobol-check-0.2.5.zip deleted file mode 100644 index 71a86fe5..00000000 Binary files a/build/distributions/cobol-check-0.2.5.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.7.zip b/build/distributions/cobol-check-0.2.7.zip deleted file mode 100644 index 82bf06c7..00000000 Binary files a/build/distributions/cobol-check-0.2.7.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.8.zip b/build/distributions/cobol-check-0.2.8.zip deleted file mode 100644 index fe845773..00000000 Binary files a/build/distributions/cobol-check-0.2.8.zip and /dev/null differ diff --git a/build/distributions/cobol-check-0.2.9.zip b/build/distributions/cobol-check-0.2.9.zip deleted file mode 100644 index 86766430..00000000 Binary files a/build/distributions/cobol-check-0.2.9.zip and /dev/null differ diff --git a/cobolcheck b/cobolcheck old mode 100755 new mode 100644 diff --git a/config.properties b/config.properties index f3b8f2ba..4812bd75 100644 --- a/config.properties +++ b/config.properties @@ -58,7 +58,7 @@ cobolcheck.append.rules = null # cobolcheck.test.program.path = /home/myName/temp # cobolcheck.test.program.path = c:\\Developer\\temp # cobolcheck.test.program.path = c:/Developer/temp -cobolcheck.test.program.path = ./ +cobolcheck.test.program.path = ./testruns #--------------------------------------------------------------------------------------------------------------------- # Suffix to append to the name of each program under test to produce the name of the corresponding @@ -73,13 +73,13 @@ cobolcheck.test.program.name = CC##99.CBL # When true, COBOL Check will report the unmocked calls, and the test summary will contain the number of unmocked calls. # Default: false #--------------------------------------------------------------------------------------------------------------------- -cobolcheck.test.unmockcall.display = false +cobolcheck.test.unmockcall.display =false #--------------------------------------------------------------------------------------------------------------------- # Path for the generated testsuite parse error log # Default: ./ #--------------------------------------------------------------------------------------------------------------------- -testsuite.parser.error.log.path = ./ +testsuite.parser.error.log.path = ./testruns #--------------------------------------------------------------------------------------------------------------------- # Name of the generated testsuite parse error log file - with extension @@ -132,7 +132,7 @@ test.suite.directory = src/test/cobol #--------------------------------------------------------------------------------------------------------------------- # Location of test output. File extension is determined by a given format. #--------------------------------------------------------------------------------------------------------------------- -test.results.file = output/testResults +test.results.file = testruns/testResults #--------------------------------------------------------------------------------------------------------------------- # Determines the format of the test results written to the output file. @@ -173,7 +173,7 @@ application.copybook.filename.suffix = CBL,cbl,COB,cob,CPY,cpy # This is the relative or absolute path of the concatenated file. If not specified, the default # is "./ALLTESTS" relative to the directory in which Cobol Check was started. #--------------------------------------------------------------------------------------------------------------------- -concatenated.test.suites = ./ALLTESTS +concatenated.test.suites = ./testruns/ALLTESTS #--------------------------------------------------------------------------------------------------------------------- # The GnuCOBOL compiler has a lot of different compile options. diff --git a/approval-test-expected.txt b/expected-output.txt similarity index 100% rename from approval-test-expected.txt rename to expected-output.txt diff --git a/src/main/cobol/MOCKTEST.CBL b/src/main/cobol/MOCKTEST.CBL index 0a03711f..103629fc 100644 --- a/src/main/cobol/MOCKTEST.CBL +++ b/src/main/cobol/MOCKTEST.CBL @@ -10,7 +10,7 @@ WORKING-STORAGE SECTION. 01 FILLER. 05 VALUE-1 PIC X(80). - 05 VALUE-2 PIC X(80). + 05 VALUE-2 PIC X(80) Value 'initial'. 05 VALUE-3 PIC X(80). 05 TEMP PIC X(80). @@ -85,15 +85,15 @@ CALL 'PROG1' . - 610-MAKE-CALL-VALUE-IN-STRUCTURE. - MOVE "1" to ACTION-PARAM - MOVE "2" to BOOK-PARAM - MOVE "3" to OUTPUT-VALUE - CALL 'MYCOBOL' USING ACTION-PARAM, - BOOK-PARAM IN BOOK-STRUCTURE, - OUTPUT-PARAM + 610-CALL-VALUE-IN-STRUCTURE. + MOVE "1" to action-value + MOVE "2" to book-value + MOVE "3" to output-value + CALL 'MYCOBOL' USING action-value, + book-value IN BOOK-STRUCTURE, + output-value END-CALL - MOVE OUTPUT-PARAM TO VALUE-1 + MOVE OUTPUT-VALUE TO VALUE-1 . 700-MAKE-CALL. @@ -115,7 +115,7 @@ BY CONTENT VALUE-1, BY VALUE VALUE-2, VALUE-3 - CALL 'PROG3' USING VALUE-1. + CALL 'PROG3' USING VALUE-1. 900-MAKE-CALL. CALL 'PROGRAM' USING VALUE-1 diff --git a/src/main/cobol/REPLAC.CBL b/src/main/cobol/REPLAC.CBL new file mode 100644 index 00000000..472fd67a --- /dev/null +++ b/src/main/cobol/REPLAC.CBL @@ -0,0 +1,65 @@ + IDENTIFICATION DIVISION. + PROGRAM-ID. GREETING. + ***************************************************************** + * Trivial program to exercise CobolCheck. + * With regards to REPLACE statement. + ***************************************************************** + ENVIRONMENT DIVISION. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + DATA DIVISION. + WORKING-STORAGE SECTION. + + REPLACE ==:PROGRAM:== BY ==REPDEMO3== + ==:REQUEST:== BY ==UPDATE-MY-ADVANCED-REQUEST== + ==:RESPONSE:== BY ==UPDATE-MY-ADVANCED-RESPONSE==. + +123456 01 FILLER. + 05 WS-COUNT PIC S9(5) COMP-3. + 05 FILLER PIC X VALUE 'G'. + 88 MESSAGE-IS-GREETING VALUE 'G'. + 88 MESSAGE-IS-FAREWELL VALUE 'F'. + 01 WS-FRIEND PIC X(10) VALUE SPACES. + 01 WS-GREETING. + 10 FILLER PIC X(07) VALUE 'Hello, '. + 10 WS-USER-NAME PIC X(05) VALUE SPACES. + 10 FILLER PIC X VALUE '!'. + 01 WS-FAREWELL. + 10 FILLER PIC X(15) VALUE 'See you later, '. + 10 WS-USER-NAME PIC X(09) VALUE SPACES. + 10 FILLER PIC X VALUE '!'. + + 01 :program:-param PIC X(10) VALUE "program". + 01 :request:-data PIC X(10) VALUE "data data". + 01 :response:-element PIC X(10) VALUE ".response.". + + PROCEDURE DIVISION. + + ACCEPT WS-FRIEND. + + + 2000-SPEAK. + IF MESSAGE-IS-GREETING + IF WS-FRIEND EQUAL SPACES + MOVE 'World' TO WS-USER-NAME OF WS-GREETING + ELSE + MOVE WS-FRIEND TO WS-USER-NAME OF WS-GREETING + END-IF + END-IF + IF MESSAGE-IS-FAREWELL + IF WS-FRIEND EQUAL SPACES + MOVE 'alligator!' TO WS-USER-NAME OF WS-FAREWELL + ELSE + MOVE WS-FRIEND TO WS-USER-NAME OF WS-FAREWELL + END-IF + END-IF + . + + 3000-SAY-NO-MORE. + DISPLAY WS-GREETING :PROGRAM:-param + DISPLAY WS-FAREWELL :PROGRAM:-param :REQUEST:-data + DISPLAY :PROGRAM:-param :REQUEST:-data :RESPONSE:-element + . + + 9999-END. + . diff --git a/src/main/cobol/WS88LEVEL.CBL b/src/main/cobol/WS88LEVEL.CBL new file mode 100644 index 00000000..586eb189 --- /dev/null +++ b/src/main/cobol/WS88LEVEL.CBL @@ -0,0 +1,113 @@ + ID DIVISION. + PROGRAM-ID. LV88TEST. + AUTHOR. TNP. + DATE-WRITTEN. 12.05.2025. + + ENVIRONMENT DIVISION. + CONFIGURATION SECTION. + SPECIAL-NAMES. + DECIMAL-POINT IS COMMA. + / + INPUT-OUTPUT SECTION. + FILE-CONTROL. + SELECT LINEFILE ASSIGN TO UT-S-LINEFILE + ORGANIZATION IS SEQUENTIAL + ACCESS MODE IS SEQUENTIAL + FILE STATUS STATUS-LINEFILE. + + DATA DIVISION. + FILE SECTION. + FD LINEFILE + BLOCK CONTAINS 0 RECORDS + RECORD CONTAINS 160 CHARACTERS + LABEL RECORD STANDARD + RECORDING F + DATA RECORD LINIE1. + 01 LINIE1. + 02 FD-88-LEVELS PIC X. + 88 FD-88-value-1 VALUE 'W'. + 88 FD-88-value-2 VALUE 'X'. + 88 FD-88-value-3 VALUE 'Y'. + 88 FD-88-value-4 VALUE 'Z'. + 88 FD-88-ALL VALUE 'Z', + 'Y', + 'X', + 'W'. + + 02 FD-88-LEVELS-NUMERIC PIC 99. + 88 FD-88-NUMERIC-LOW + VALUE 0 + through 9. + 88 FD-88-NUMERIC-MEDIUM + VALUE 10 + through 49. + 88 FD-88-NUMERIC-HIGH + VALUE 50 + through 99. + 88 FD-88-NUMERIC-MED-ODD + VALUE 11, 13, 15, 17, 19, + 21, 23, 25, 27, 29, + 31, 33, 35, 37, 39, + 41, 43, 45, 47, 49. + 88 FD-88-NUMERIC-MED-EVEN + VALUE 12, 14, 16, 18, + 22, 24, 26, 28, + 32, 34, 36, 38, + 42, 44, 46, 48. + *----------------------------------------------------------------- + WORKING-STORAGE SECTION. + *----------------------------------------------------------------- + 01 STATUS-LINEFILE PIC 99 VALUE ZERO. + 01 WORK-FIELDS. + 03 WS-PROG-NAME PIC X(8) VALUE 'LV88TEST'. + 77 WS-OMEGA PIC X. + 77 WS-SUBPROGRAM-NAME PIC X(08). + + 01 WS-88-LEVELS PIC X. + 88 level-88-value-1 VALUE 'A'. + 88 level-88-value-2 VALUE 'B'. + 88 level-88-value-3 VALUE 'C'. + 88 level-88-value-4 VALUE 'D'. + 88 level-88-ALL VALUE 'D', + 'C', + 'B', + 'A'. + + 01 WS-88-LEVELS-NUMERIC PIC 99. + 88 SW-88-NUMERIC-LOW + VALUE 0 + through 9. + 88 SW-88-NUMERIC-MEDIUM + VALUE 10 + through 49. + 88 SW-88-NUMERIC-HIGH + VALUE 50 + through 99. + 88 SW-88-NUMERIC-MED-ODD + VALUE 11, 13, 15, 17, 19, + 21, 23, 25, 27, 29, + 31, 33, 35, 37, 39, + 41, 43, 45, 47, 49. + 88 SW-88-NUMERIC-MED-EVEN + VALUE 12, 14, 16, 18, + 22, 24, 26, 28, + 32, 34, 36, 38, + 42, 44, 46, 48. + + + PROCEDURE DIVISION. + PERFORM 001-INITIALIZE + + DISPLAY SPACE + DISPLAY "TESTING 88-LEVELS IN FD and WORKING-STORAGE" + DISPLAY + 'DEMONSTRATE WS 88 STATEMENT are parsed correctly' + DISPLAY SPACES + . + + 001-INITIALIZE SECTION. + MOVE 'Z' TO WS-OMEGA + MOVE 'LV88SUB ' TO WS-SUBPROGRAM-NAME + OPEN OUTPUT LINEFILE + . + diff --git a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpander.java b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpander.java index c574615e..c642950a 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpander.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpander.java @@ -9,25 +9,27 @@ import org.openmainframeproject.cobolcheck.services.cobolLogic.CobolLine; import org.openmainframeproject.cobolcheck.services.cobolLogic.Interpreter; import org.openmainframeproject.cobolcheck.services.cobolLogic.TokenExtractor; +import org.openmainframeproject.cobolcheck.services.log.Log; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; /** * Expand copybooks referenced by the code under test. - * + *

* In the general use case, COPY statements are left alone and the compiler handles expansion. - * + *

* Cobol-check runs as a precompiler and does not "see" the expanded Cobol source code. * There are two cases in which cobol-check may need to "see" the expanded code. - * + *

* 1. If source statements pertinent to a mock are contained in copybooks, cobol-check needs to * be able to comment-out those statements in the merged test program. - * + *

* 2. IBM z/OS compilers support nested COPY statements with the REPLACING option. Off-platform * compilers written by others do not support this, as it is an IBM extension to Cobol. When the * code under test uses this feature, and cobol-check is running on a platform other than z/OS, @@ -47,10 +49,6 @@ public CopybookExpander() { } - public List expand(List expandedLines, String copybookFilename) throws IOException { - return expand(expandedLines, copybookFilename, new StringTuple(null, null)); - } - public List expand(List expandedLines, String copybookFilename, StringTuple... textReplacement) throws IOException { String fullPath = PathHelper.findFilePath(pathToCopybooks, copybookFilename, copybookFilenameSuffixes); @@ -89,8 +87,27 @@ public List expand(List expandedLines, String copybookFilename, return expandedLines; } - private String commentOut(String sourceLine) { + /** + * Comments out a source line by placing an asterisk in column 7. + * If the line is shorter than 7 characters, it is returned unchanged. + * @param sourceLine + * @return + */ + String commentOut(String sourceLine) { + // Create StringBuilder from sourceLine ensuring it is at least 7 characters long + if (sourceLine == null || sourceLine.length() < 7) { + return sourceLine; + } StringBuilder tempLine = new StringBuilder(sourceLine); + // IF the column 7 is not space, then we are probably dealing with a + // non-standard source format. In that case, we will throw an exception. + if (tempLine.charAt(6) != ' ' && tempLine.charAt(6) != '*') { + throw new PossibleInternalLogicErrorException( + Messages.get("ERR034", + sourceLine)); + } + + // put an asterisk in column 7 (index 6) tempLine.setCharAt(6, '*'); return tempLine.toString(); } @@ -123,8 +140,7 @@ private String getPathToCopybooks() { return PathHelper.endWithFileSeparator(Config.getCopyBookSourceDirectoryPathString()); } - public List expandDB2(List expandedLines, String copybookFilename, - StringTuple... textReplacement) throws IOException { + public List expandDB2(List expandedLines, String copybookFilename) throws IOException { String fullPath = PathHelper.findFilePath(pathToCopybooks, copybookFilename, copybookFilenameSuffixes); if (fullPath == null) throw new IOException("could not find copybook " + copybookFilename + " in " + pathToCopybooks); @@ -147,4 +163,63 @@ public List expandDB2(List expandedLines, String copybookFilenam return expandedLines; } + /** + * Returns the lines included by the INCLUDE statement + * @param 'exec sql' statement with an INCLUDE clause + * @return list of lines included by the INCLUDE statement + * @throws IOException + */ + public List getIncludedLines(String line) throws IOException { + List expandedLines = new ArrayList<>(); + String copybookName = extractCopybookNameFromCopyStatement(line); + return this.expandDB2(expandedLines, copybookName); + } + + + String extractCopybookNameFromCopyStatement(String line) { + String copybookName ; + int copybookNameStartPosition = findCopybookNamePositionInCopyStatement(line); + int copybookNameEndPosition = findCopyBookNameEndPositionInCopyStatement(line, copybookNameStartPosition); + if (copybookNameStartPosition >= 0 && copybookNameEndPosition > copybookNameStartPosition) { + copybookName = line.substring(copybookNameStartPosition, copybookNameEndPosition).trim(); + if (copybookName.endsWith(Constants.PERIOD)) { + copybookName = copybookName.substring(0, copybookName.length() - 1); + } + } else { + throw new PossibleInternalLogicErrorException( + Messages.get("ERR008", + line, + "LineRepository.extractCopybookNameFromCopyStatement(line)", + line)); + } + return copybookName; + } + + int findCopybookNamePositionInCopyStatement(String line) { + int copybookNamePosition = -1; + int copyWordPosition = line.indexOf(Constants.INCLUDE); + if (copyWordPosition >= 0) { + copybookNamePosition = copyWordPosition + Constants.INCLUDE.length() + 1; + } + return copybookNamePosition; + } + + int findCopyBookNameEndPositionInCopyStatement(String line, int copybookNameStartPosition) { + int copybookNameEndPosition = -1; + if (copybookNameStartPosition >= 0) { + int periodPosition = line.indexOf(Constants.PERIOD, copybookNameStartPosition); + int spacePosition = line.indexOf(Constants.SPACE, copybookNameStartPosition); + if (periodPosition >= 0 && spacePosition >= 0) { + copybookNameEndPosition = Math.min(periodPosition, spacePosition); + } else if (periodPosition >= 0) { + copybookNameEndPosition = periodPosition; + } else if (spacePosition >= 0) { + copybookNameEndPosition = spacePosition; + } else { + copybookNameEndPosition = line.length(); + } + } + return copybookNameEndPosition; + } + } diff --git a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/InterpreterController.java b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/InterpreterController.java index 0f00c174..a34fd75f 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/InterpreterController.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/InterpreterController.java @@ -10,7 +10,6 @@ import org.openmainframeproject.cobolcheck.services.log.Log; import org.openmainframeproject.cobolcheck.services.platform.Platform; import org.openmainframeproject.cobolcheck.services.platform.PlatformLookup; -import org.openmainframeproject.cobolcheck.services.RunInfo; import java.io.BufferedReader; import java.io.IOException; @@ -27,7 +26,6 @@ public class InterpreterController { private String possibleMockIdentifier; private String possibleMockType; private List possibleMockArgs; - private List extractedCopyBook; private boolean insideSectionOrParagraphMockBody; private TreeMap currentDataStructure; private final String stubTag; @@ -69,6 +67,11 @@ public DataType getNumericFieldDataTypeFor(String fieldName) { return numericFields.dataTypeOf(fieldName); } + /** + * returns the NumericFields object, which contains information about + * all numeric fields found in the DATA DIVISION of the program under test. + * @return + */ public NumericFields getNumericFields() { return numericFields; } @@ -263,7 +266,9 @@ public void closeReader() { * @param line - The line the update is based upon */ private void updateDependencies(CobolLine line) throws IOException { + reader.updateState(); + updateLineRepository(line); List currentStatement = new ArrayList<>(); @@ -273,6 +278,8 @@ private void updateDependencies(CobolLine line) throws IOException { currentStatement.add(line); } + updateLineRepositoryUpdateSecondPass(line); + if (reader.isFlagSet(Constants.SPECIAL_NAMES_PARAGRAPH)) { updateDecimalPointIsComma(line); } @@ -432,6 +439,8 @@ private String generateVariableNameBasedOnDataStructure(TreeMap * @param line - current source line */ private void updateLineRepository(CobolLine line) throws IOException { + List extractedCopyBook; + if (reader.isFlagSet(Constants.FILE_CONTROL)) { lineRepository.addFileControlStatement(line.getUnNumberedString()); @@ -450,7 +459,7 @@ private void updateLineRepository(CobolLine line) throws IOException { lineRepository.addFileSectionStatement(line.getUnNumberedString()); } } - + if (reader.isFlagSet(Constants.WORKING_STORAGE_SECTION) && line.containsToken(Constants.EXEC_SQL_TOKEN)) { String statement = reader.readStatementAsOneLine().getTrimmedString().replaceAll("\\s+", " "); if (statement.startsWith(Constants.EXEC_SQL_TOKEN + " " + Constants.INCLUDE)) { @@ -460,13 +469,14 @@ private void updateLineRepository(CobolLine line) throws IOException { if (statement.contains("SQLCA") || statement.contains("SQLDA")) return; default: - extractedCopyBook = lineRepository.addExpandedCopyDB2Statements(reader.readStatementAsOneLine()); + extractedCopyBook = lineRepository.getExpandedCopyDB2Statements(statement); + lineRepository.addAllLinesFromCopyDB2StatementsToFileSectionStatements(extractedCopyBook); for (int i = 0; i < extractedCopyBook.size(); i++) { CobolLine cobolLine = new CobolLine(extractedCopyBook.get(i), tokenExtractor); List currentStatement = new ArrayList<>(); currentStatement.add(cobolLine); this.currentDataStructure = updateCurrentDataStructure(currentStatement, currentDataStructure); - updateNumericFields(cobolLine); + updateNumericFields(cobolLine); } break; } @@ -474,6 +484,31 @@ private void updateLineRepository(CobolLine line) throws IOException { } } + /** + * If we are in the FILE SECTION and have read a multiline statement, we need to + * add all lines except the first one, to the list of file section statements. + * The first line is already added as part of reading the statement in the first pass. + * + * @param line - current source line + */ + private void updateLineRepositoryUpdateSecondPass(CobolLine line) throws IOException { + + if (reader.isFlagSet(Constants.FILE_SECTION) && reader.isFlagSet(Constants.FD_TOKEN)) { + if (reader.isFlagSet(Constants.LEVEL_01_TOKEN)) { + if ( this.hasStatementBeenRead()) { + List lines = reader.getCurrentStatement(); + + // we Skip first line, it is already added as part of reading the statement in the first pass + for (int idx = 1; idx < lines.size(); idx++) { + // updateNumericFields(l); + lineRepository.addFileSectionStatement(lines.get(idx).getUnNumberedString()); + } + } + } + } + } + + /** * If the given line contains a SELECT token, the file identifier will be added, waiting for * a mapping to a corresponding file status. diff --git a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepository.java b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepository.java index 6f332ab3..3340dc6f 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepository.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepository.java @@ -5,7 +5,7 @@ import org.openmainframeproject.cobolcheck.services.Constants; import org.openmainframeproject.cobolcheck.services.Messages; import org.openmainframeproject.cobolcheck.services.StringTuple; -import org.openmainframeproject.cobolcheck.services.cobolLogic.CobolLine; +import org.openmainframeproject.cobolcheck.services.log.Log; import java.io.IOException; import java.util.ArrayList; @@ -50,9 +50,6 @@ Map getFileIdentifiersAndStatuses() { List getCopyTokens() { return copyTokens; } - void setCopyTokens(List copyTokens) { - this.copyTokens = copyTokens; - } void addFileControlStatement(String statement){ if (fileControlStatements == null){ @@ -68,9 +65,6 @@ void addFileSectionStatement(String statement){ fileSectionStatements.add(statement); } - void putFileIdentifierAndStatus(String key, String value){ - fileIdentifiersAndStatuses.put(key, value); - } void addFileIdentifierWithNoStatus(String identifier){ fileIdentifiersAndStatuses.put(identifier, Constants.EMPTY_STRING); currentExpectFileIdentifier = identifier; @@ -79,14 +73,9 @@ void addStatusForLastSetIdentifier(String status){ fileIdentifiersAndStatuses.put(currentExpectFileIdentifier, status); } - void addCopyToken(String token){ - if (copyTokens == null){ - copyTokens = new ArrayList<>(); - } - copyTokens.add(token); - } - void addAccumulatedTokensFromCopyStatementToCopyTokens(String line) { + // warn on the number og tokens collected + Log.warn("addAccumulatedTokensFromCopyStatementToCopyTokens: " + line); if (copyTokens == null) { copyTokens = new ArrayList<>(); } @@ -99,6 +88,9 @@ void addAccumulatedTokensFromCopyStatementToCopyTokens(String line) { } List addExpandedCopyStatementsToFileSectionStatements() { + // warn on the number og tokens collected + Log.warn("addExpandedCopyStatementsToFileSectionStatements: " + copyTokens.size() + " tokens collected"); + for (int i = 0 ; i < copyTokens.size() ; i++) { if (copyTokens.get(i).equals(Constants.EMPTY_STRING)) { copyTokens.remove(i); @@ -127,24 +119,33 @@ List addExpandedCopyStatementsToFileSectionStatements() { try { copyLines = copybookExpander.expand(copyLines, copybookName, replacingValues); } catch (IOException ioException) { - ioException.printStackTrace(); + throw new PossibleInternalLogicErrorException("addExpandedCopyStatementsToFileSectionStatements",ioException); } fileSectionStatements.addAll(copyLines); return copyLines; } - List addExpandedCopyDB2Statements(CobolLine line) throws IOException { - List copyLines = new ArrayList<>(); + /** + * Expands an 'EXEC SQL' statement into multiple lines + * by locating and reading the INCLUDED copybook. + * @param line + * @return + * @throws IOException + */ + List getExpandedCopyDB2Statements(String line) throws IOException { + List copyLines; CopybookExpander copybookExpander = new CopybookExpander(); - String copybookName = line.getToken(2); - StringTuple replacingValues = new StringTuple(null, null); try { - copyLines = copybookExpander.expandDB2(copyLines, copybookName, replacingValues); + copyLines = copybookExpander.getIncludedLines(line); } catch (IOException ioEx) { throw new CopybookCouldNotBeExpanded(ioEx); } - fileSectionStatements.addAll(copyLines); return copyLines; } + + + public void addAllLinesFromCopyDB2StatementsToFileSectionStatements(List extractedCopyBook) { + this.fileSectionStatements.addAll(extractedCopyBook); + } } diff --git a/src/main/java/org/openmainframeproject/cobolcheck/features/writer/CobolWriter.java b/src/main/java/org/openmainframeproject/cobolcheck/features/writer/CobolWriter.java index ea728a5b..9a508c42 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/writer/CobolWriter.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/writer/CobolWriter.java @@ -3,6 +3,7 @@ import org.openmainframeproject.cobolcheck.services.Config; import org.openmainframeproject.cobolcheck.services.cobolLogic.Interpreter; import org.openmainframeproject.cobolcheck.services.StringHelper; +import org.openmainframeproject.cobolcheck.services.log.Log; import java.io.IOException; import java.io.Writer; @@ -39,6 +40,7 @@ void writeLine(String line) throws IOException { storedLines.add(line); else writer.write(line); + writer.flush(); } else { //We need to check if this line is to be commented out or if it is already a comment @@ -95,12 +97,8 @@ void writeStubbedLine(String line) throws IOException { writeLine(StringHelper.stubLine(line, stubTag)); } - void writeFormattedLine(String format, Object... args) throws IOException { - writeLine(String.format(format, args)); - } - /** - * Writes all the given lines of cobol code to the test output file. If the any of the lines + * Writes all the given lines of cobol code to the test output file. If any of the lines * are too long for cobol to handle, it will be correctly split into multiple lines. * * @param lines - lines to be written diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/Interpreter.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/Interpreter.java index 80cf22dd..8309b97d 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/Interpreter.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/Interpreter.java @@ -36,7 +36,6 @@ public static int getSequenceNumberAreaIndex(){ return sequenceNumberAreaEnd; } - //TODO: Speed up method by adding 'else if's and putting 'if's inside 'if's /** * Sets flags based on a line, to be able to know which kinds of source * statements to look for when reading and interpreting lines. @@ -50,92 +49,88 @@ public static String setFlagsForCurrentLine(CobolLine line, CobolLine nextLine, if (line.containsToken(Constants.IDENTIFICATION_DIVISION)) { state.setFlagFor(Constants.IDENTIFICATION_DIVISION); partOfProgram = Constants.IDENTIFICATION_DIVISION; - } - if (line.containsToken(Constants.ENVIRONMENT_DIVISION)) { + + } else if (line.containsToken(Constants.ENVIRONMENT_DIVISION)) { state.setFlagFor(Constants.ENVIRONMENT_DIVISION); partOfProgram = Constants.ENVIRONMENT_DIVISION; - } - if (line.containsToken(Constants.CONFIGURATION_SECTION)) { + + } else if (line.containsToken(Constants.CONFIGURATION_SECTION)) { state.setFlagFor(Constants.CONFIGURATION_SECTION); partOfProgram = Constants.CONFIGURATION_SECTION; - } - if (line.containsToken(Constants.SPECIAL_NAMES_PARAGRAPH)) { + + } else if (line.containsToken(Constants.SPECIAL_NAMES_PARAGRAPH)) { state.setFlagFor(Constants.SPECIAL_NAMES_PARAGRAPH); partOfProgram = Constants.SPECIAL_NAMES_PARAGRAPH; - } - if (line.containsToken(Constants.INPUT_OUTPUT_SECTION)) { + + } else if (line.containsToken(Constants.INPUT_OUTPUT_SECTION)) { state.setFlagFor(Constants.INPUT_OUTPUT_SECTION); partOfProgram = Constants.INPUT_OUTPUT_SECTION; - } - if (line.containsToken(Constants.FILE_CONTROL)) { + + } else if (line.containsToken(Constants.FILE_CONTROL)) { state.setFlagFor(Constants.FILE_CONTROL); partOfProgram = Constants.FILE_CONTROL; - } - if (line.containsToken(Constants.DATA_DIVISION)) { + + } else if (line.containsToken(Constants.DATA_DIVISION)) { state.setFlagFor(Constants.DATA_DIVISION); partOfProgram = Constants.DATA_DIVISION; - } - if (line.containsToken(Constants.PROCEDURE_DIVISION)) { + + } else if (line.containsToken(Constants.PROCEDURE_DIVISION)) { state.setFlagFor(Constants.PROCEDURE_DIVISION); partOfProgram = Constants.PROCEDURE_DIVISION; - } - if (line.containsToken(Constants.FILE_SECTION)) { + + } else if (line.containsToken(Constants.FILE_SECTION)) { state.setFlagFor(Constants.FILE_SECTION); partOfProgram = Constants.FILE_SECTION; - } - if (line.containsToken(Constants.LOCAL_STORAGE_SECTION)) { + + } else if (line.containsToken(Constants.LOCAL_STORAGE_SECTION)) { state.setFlagFor(Constants.LOCAL_STORAGE_SECTION); partOfProgram = Constants.LOCAL_STORAGE_SECTION; - } - if (line.containsToken(Constants.LINKAGE_SECTION)) { + + } else if (line.containsToken(Constants.LINKAGE_SECTION)) { state.setFlagFor(Constants.LINKAGE_SECTION); partOfProgram = Constants.LINKAGE_SECTION; - } - if (line.containsToken(Constants.WORKING_STORAGE_SECTION)) { + + } else if (line.containsToken(Constants.WORKING_STORAGE_SECTION)) { state.setFlagFor(Constants.WORKING_STORAGE_SECTION); partOfProgram = Constants.WORKING_STORAGE_SECTION; - } - if (line.containsToken(Constants.SELECT_TOKEN)) { - state.unsetFlagFor(Constants.SELECT_TOKEN); - state.setFlagFor(Constants.SELECT_TOKEN); - partOfProgram = Constants.SELECT_TOKEN; - } - if (line.containsToken(Constants.FILE_STATUS_TOKEN)) { + + } else if (line.containsToken(Constants.FILE_STATUS_TOKEN)) { state.setFlagFor(Constants.FILE_STATUS_TOKEN); partOfProgram = Constants.FILE_STATUS_TOKEN; - } - if (line.containsToken(Constants.IS_TOKEN)) { - state.setFlagFor(Constants.IS_TOKEN); - partOfProgram = Constants.IS_TOKEN; - } - if (line.containsToken(Constants.IS_TOKEN)) { + + } else if (line.containsToken(Constants.IS_TOKEN)) { state.setFlagFor(Constants.IS_TOKEN); partOfProgram = Constants.IS_TOKEN; - } - if (line.containsToken(Constants.FD_TOKEN)) { - state.unsetFlagFor(Constants.FD_TOKEN); - state.setFlagFor(Constants.FD_TOKEN); - partOfProgram = Constants.FD_TOKEN; - } - if (line.containsToken(Constants.LEVEL_01_TOKEN)) { + + } else if (line.containsToken(Constants.LEVEL_01_TOKEN)) { state.setFlagFor(Constants.LEVEL_01_TOKEN); partOfProgram = Constants.LEVEL_01_TOKEN; - } - if (line.containsToken(Constants.COPY_TOKEN)) { + + } else if (line.containsToken(Constants.COPY_TOKEN)) { state.setFlagFor(Constants.COPY_TOKEN); partOfProgram = Constants.COPY_TOKEN; - } - if (line.containsToken(Constants.SECTION_TOKEN)) { +// tokens with unset flags, as they can appear multiple times + } else if (line.containsToken(Constants.SELECT_TOKEN)) { + state.unsetFlagFor(Constants.SELECT_TOKEN); + state.setFlagFor(Constants.SELECT_TOKEN); + partOfProgram = Constants.SELECT_TOKEN; + + } else if (line.containsToken(Constants.SECTION_TOKEN)) { state.unsetFlagFor(Constants.SECTION_TOKEN); state.setFlagFor(Constants.SECTION_TOKEN); partOfProgram = Constants.SECTION_TOKEN; - } - if (isParagraphHeader(line, nextLine, state)) { + + } else if (isParagraphHeader(line, nextLine, state)) { state.unsetFlagFor(Constants.PARAGRAPH_TOKEN); state.setFlagFor(Constants.PARAGRAPH_TOKEN); partOfProgram = Constants.PARAGRAPH_TOKEN; - } - if (line.containsToken(Constants.CALL_TOKEN)) { + + } else if (line.containsToken(Constants.FD_TOKEN)) { + state.unsetFlagFor(Constants.FD_TOKEN); + state.setFlagFor(Constants.FD_TOKEN); + partOfProgram = Constants.FD_TOKEN; + + } else if (line.containsToken(Constants.CALL_TOKEN)) { state.unsetFlagFor(Constants.CALL_TOKEN); state.setFlagFor(Constants.CALL_TOKEN); partOfProgram = Constants.CALL_TOKEN; diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/NumericFields.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/NumericFields.java index 0b8a542c..071d27fb 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/NumericFields.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/NumericFields.java @@ -30,6 +30,15 @@ public DataType dataTypeOf(String fieldName) { return fieldTypes.getOrDefault(fieldName, DataType.ALPHANUMERIC); } + /** + * Returns the count of all fields known + * @return integer count of known fields + */ + public int getNumberOfFields() { + if (fieldTypes != null) return fieldTypes.size(); + return 0; + } + public void setDataTypeOf(String fieldName, DataType dataType) { argumentCheck(fieldName, "ERR028"); argumentCheck(dataType, "ERR029"); diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/Replace.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/Replace.java index 7b547e41..0dfc093a 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/Replace.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/Replace.java @@ -1,6 +1,8 @@ package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; +import org.openmainframeproject.cobolcheck.services.Config; import org.openmainframeproject.cobolcheck.services.filehelpers.EncodingIO; +import org.openmainframeproject.cobolcheck.services.filehelpers.FilePermission; import org.openmainframeproject.cobolcheck.services.log.Log; import org.openmainframeproject.cobolcheck.services.log.LogLevel; @@ -45,6 +47,13 @@ public class Replace { + COBOL_COMMENT_INDICATOR + ")(.+)"); private static final int SOURCE_COMMENT_INDICATOR = 2; + /** + * Suffix for the replaced file name. + */ + private static final String FILE_PERIOD = "."; + private static final String REPLACED = FILE_PERIOD + "replaced"; + private static final String DEFAULT_EXTENSION = "CBL"; + /** * The state of the REPLACE statement. @@ -160,7 +169,7 @@ private static void reset() { public static String replaceInProgram(File program) { // write the replaced program back to disk - String newFileName = program+"_MOD"; + String newFileName = getOutputFileName(program.getAbsolutePath()); Log.warn("Replace.replaceInProgram(): Writing the COBOL program file: " + newFileName); try { BufferedWriter writer = (BufferedWriter) EncodingIO.getWriterWithCorrectEncoding(newFileName); @@ -178,6 +187,7 @@ public static String replaceInProgram(File program) { } catch (IOException e) { Log.error("Replace.replaceInProgram(): Error writing the COBOL program file: " + program); } + updateFilePermissions(newFileName); return newFileName; } @@ -186,4 +196,67 @@ public static void showReplaceSets() { Log.info("Replace.showReplaceSets():" + replaceSet.toString()); } } + + static String getOutputFileName(String inputFileName) { + String newFileNAme = getFilenameWithoutPath(inputFileName); + + String outputDir = Config.getGeneratedTestCodePath(); + + if (!outputDir.endsWith(File.separator)) { + outputDir = outputDir + File.separator; + } + + newFileNAme = outputDir + getFileNameWithoutExtension(newFileNAme) + + REPLACED + FILE_PERIOD + getFileExtension(newFileNAme); + + return newFileNAme; + } + + /** + * Set the file permissions of the generated file according to the configuration + * so other users can read/write/execute the file if so configured. + * @param newFileName + */ + private static void updateFilePermissions(String newFileName) { + String permissions = Config.getGeneratedFilesPermissionAll(); + FilePermission.setFilePermissionForAllUsers(new File(newFileName), permissions); + } + + /** Get the file name without the path. + * If there is no path, return the file name as is. + * @param filePath the file name with or without path + * @return the file name without the path + */ + static String getFilenameWithoutPath(String filePath) { + filePath = filePath.trim(); + filePath = filePath.replace("\\", "/"); + int lastSlash = filePath.lastIndexOf('/'); + return filePath.substring(lastSlash + 1); + } + + /** Get the file name without the extension. + * If there is no extension, return the file name as is. + * @param fileName the file name with or without path and extension + * @return the file name without the extension + */ + static String getFileNameWithoutExtension(String fileName) { + int lastDot = fileName.lastIndexOf('.'); + if (lastDot == -1) { + return fileName; + } + return fileName.substring(0, lastDot); + } + + /** Get the file extension. + * If there is no extension, return ".cbl" as default extension. + * @param fileName the file name with or without path and extension + * @return the file extension including the dot, e.g. ".cbl" + */ + static String getFileExtension(String fileName) { + int lastDot = fileName.lastIndexOf('.'); + if (lastDot == -1) { + return DEFAULT_EXTENSION; + } + return fileName.substring(lastDot + 1); + } } diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/filehelpers/PathHelper.java b/src/main/java/org/openmainframeproject/cobolcheck/services/filehelpers/PathHelper.java index 5cf440c2..476d7077 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/services/filehelpers/PathHelper.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/filehelpers/PathHelper.java @@ -8,6 +8,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Paths; import java.util.List; @@ -62,7 +63,8 @@ public static List getMatchingDirectories(String name, String path) thro Files.walkFileTree(Paths.get(path), directoryFinder); matchingDirectories = directoryFinder.getMatchingDirectories(); if (matchingDirectories.isEmpty()) { - Log.warn(Messages.get("WRN001", name, path)); + Log.error(Messages.get("ERR033", name, path)); + throw new NoSuchFileException("no directories found at " + path); } return matchingDirectories; } diff --git a/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java b/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java index 68469bbb..207beea9 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java @@ -72,8 +72,11 @@ public void prepareAndRunMerge(String programName, String testFileNames) { Replace.inspectProgram(originalSource); matchingTestDirectories = PrepareMergeController.getMatchingTestDirectoriesForProgram(programName); - //replace in the program, return the program name with the corrected source code. - programName = Replace.replaceInProgram(originalSource); + // Handle REPLACE statements in the original source code + if (Replace.isReplaceOn()) { + //replace in the program, return the file name where the corrected source code was placed. + programName = Replace.replaceInProgram(originalSource); + } for (String matchingDirectory : matchingTestDirectories) { Reader sourceReader = PrepareMergeController.getSourceReader(programName); @@ -186,8 +189,12 @@ private void writeToSource(String sourceLine) throws IOException { if (interpreter.shouldCurrentLineBeStubbed()) { if(interpreter.isReading(Constants.WORKING_STORAGE_SECTION)) { writerController.writeStubbedLine(interpreter.getCurrentLineAsStatement().getUnNumberedString()); - if (!interpreter.getFileSectionStatements().isEmpty()) + if (!interpreter.getFileSectionStatements().isEmpty()) { writerController.writeLines(interpreter.getFileSectionStatements()); + if (!(interpreter.getFileSectionStatements() == null)) { + interpreter.getFileSectionStatements().clear(); + } + } } else writerController.writeStubbedLine(sourceLine); diff --git a/src/main/resources/org/openmainframeproject/cobolcheck/messages/messages.properties b/src/main/resources/org/openmainframeproject/cobolcheck/messages/messages.properties index 1a003f20..7c28a76f 100644 --- a/src/main/resources/org/openmainframeproject/cobolcheck/messages/messages.properties +++ b/src/main/resources/org/openmainframeproject/cobolcheck/messages/messages.properties @@ -32,6 +32,8 @@ ERR029 = ERR029: NumericFields.setDataTypeOf() was called with null dataType. ERR030 = ERR030: Command line missing program argument '-p programName' . ERR031 = ERR031: A test suite with the name %1$s already exists. ERR032 = ERR032: A test case with the name %1$s already exists in the test suite %2$s. +ERR033 = ERR033: No test suite directory for program %1$s was found under directory %2$s. +ERR034 = ERR034: Not abel to put comment indicator in line %1$s. The position was in used by a literal. WRN001 = WRN001: No test suite directory for program %1$s was found under directory %2$s. WRN002 = WRN002: No test suite files were found under directory %1$s. diff --git a/src/test/approvalTest/approvaltest b/src/test/approvalTest/approvaltest new file mode 100644 index 00000000..0789605e --- /dev/null +++ b/src/test/approvalTest/approvaltest @@ -0,0 +1,17 @@ +./build/approvalTest/cobolcheck -p NUMBERS > ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p ALPHA >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p GREETING >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p FILECOPY >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p MOCKTEST >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p DB2PROG >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p MOCK >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p MOCKPARA >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p MOCKTEST >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p RETURNCODE >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p TESTNESTED >> ./build/approvalTest/actual-output.txt +./build/approvalTest/cobolcheck -p LONGLINESANDNUMBERS >> ./build/approvalTest/actual-output.txt + + + + + diff --git a/src/test/approvalTest/approvaltestWin.cmd b/src/test/approvalTest/approvaltestWin.cmd new file mode 100644 index 00000000..17249444 --- /dev/null +++ b/src/test/approvalTest/approvaltestWin.cmd @@ -0,0 +1 @@ +build/approvalTest/cobolcheck -p ALPHA DB2PROG DPICNUMBERS FILECOPY GREETING MOCK MOCKPARA MOCKTEST NUMBERS RETURNCODE TESTNESTED LONGLINESANDNUMBERS > build/approvalTest/actual-output.txt \ No newline at end of file diff --git a/src/test/approvalTest/expected-output.txt b/src/test/approvalTest/expected-output.txt new file mode 100644 index 00000000..018e799c --- /dev/null +++ b/src/test/approvalTest/expected-output.txt @@ -0,0 +1,235 @@ +TESTSUITE: +Verify Cobol Check handles numeric relations properly + PASS: 1. Equal sign with literal compare +**** FAIL: 2. Equal sign with literal compare (should fail) + EXPECTED +00000000025.7500000, WAS +00000000025.7400000 + PASS: 3. Not equal sign with literal compare +**** FAIL: 4. Not equal sign with literal compare (should fail) + EXPECTED +00000000025.7400000, WAS +00000000025.7400000 + PASS: 5. Not-equal sign with literal compare +**** FAIL: 6. Not-equal sign with literal compare (should fail) + EXPECTED +00000000013.6000000, WAS +00000000013.6000000 + PASS: 7. Not not-equal sign with literal compare +**** FAIL: 8. Not not-equal sign with literal compare (should fail) + EXPECTED +00000000013.6000000, WAS +00000000013.7000000 + PASS: 9. Equal sign with ield compare +**** FAIL: 10. Equal sign with field compare (should fail) + EXPECTED +00000000025.7500000, WAS +00000000025.7400000 + PASS: 11. Not equal sign with field compare +**** FAIL: 12. Not equal sign with field compare (should fail) + EXPECTED +00000000025.7400000, WAS +00000000025.7400000 + PASS: 13. Not-equal sign with field compare +**** FAIL: 14. Not-equal sign with field compare (should fail) + EXPECTED +00000000025.7400000, WAS +00000000025.7400000 + PASS: 15. Not not-equal sign with field compare +**** FAIL: 16. Not not-equal sign with field compare (should fail) + EXPECTED +00000000025.7400000, WAS +00000000025.7500000 + PASS: 17. Less-than sign with literal compare +**** FAIL: 18. Less-than sign with literal compare (should fail) + EXPECTED +00000000018.0660000, WAS +00000000018.0670000 + PASS: 19. Not less-than sign with literal compare +**** FAIL: 20. Not less-than sign with literal compare (should fail) + EXPECTED +00000000018.0670000, WAS +00000000018.0660000 + PASS: 21. Less-than sign with field compare +**** FAIL: 22. Less-than sign with field compare (should fail) + EXPECTED +00000000416.0720000, WAS +00000000416.0720000 + PASS: 23. Not less-than sign with field compare +**** FAIL: 24. Not less-than sign with field compare (should fail) + EXPECTED +00000000416.0720000, WAS +00000000416.0710000 + PASS: 25. Greater-than sign with literal compare +**** FAIL: 26. Greater-than sign with literal compare (should fail) + EXPECTED +00000000010.0000000, WAS +00000000009.8050000 + PASS: 27. Not greater-than sign with literal compare +**** FAIL: 28. Not greater-than sign with literal compare (should fail) + EXPECTED +00000000107.7010000, WAS +00000000107.7020000 + PASS: 29. Greater-than sign with field compare +**** FAIL: 30. Greater-than sign with field compare (should fail) + EXPECTED +00000001766.0314400, WAS +00000001766.0314300 + PASS: 31. Not greater-than sign with field to field compare +**** FAIL: 32. Not greater-than sign with field compare (should fail) + EXPECTED +00000001766.0314400, WAS +00000001766.0314500 + PASS: 33. Greater-than-or-equal-to sign with literal compare when greater + PASS: 34. Greater-than-or-equal-to sign with literal compare when equal +**** FAIL: 35. Greater-than-or-equal-to sign with literal compare (should fail) + EXPECTED +00000000010.0000000, WAS +00000000009.8050000 + PASS: 36. Not greater-than-or-equal-to sign with literal compare when less +**** FAIL: 37. Not greater-than-or-equal-to sign with literal compare when equal (should fail) + EXPECTED +00000000018.0670000, WAS +00000000018.0670000 +**** FAIL: 38. Greater-than-or-equal-to sign with literal compare when greater (should fail) + EXPECTED +00000000013.4400000, WAS +00000000013.4500000 + PASS: 39. Greater-than-or-equal-to-sign with field compare when equal + PASS: 40. Greater-than-or-equal-to-sign with field compare when greater +**** FAIL: 41. Greater-than-or-equal-to-sign with field compare when less (should fail) + EXPECTED +00000000475.0620000, WAS +00000000475.0610000 + PASS: 42. Not greater-than-or-equal-to-sign with field compare when less +**** FAIL: 43. Not greater-than-or-equal-to-sign with field compare when equal (should fail) + EXPECTED +00000000475.0620000, WAS +00000000475.0620000 +**** FAIL: 44. Not greater-than-or-equal-to-sign with field compare when greater (should fail) + EXPECTED +00000000475.0620000, WAS +00000000475.0630000 + PASS: 45. Less-than-or-equal-to-sign with field compare when equal + PASS: 46. Less-than-or-equal-to-sign with field compare when less +**** FAIL: 47. Less-than-or-equal-to-sign with field compare when greater (should fail) + EXPECTED +00000000475.0620000, WAS +00000000475.0630000 + PASS: 48. Not less-than-or-equal-to-sign with field compare when greater +**** FAIL: 49. Not greater-than-or-equal-to-sign with field compare when equal (should fail) + EXPECTED +00000000475.0620000, WAS +00000000475.0620000 +**** FAIL: 50. Not greater-than-or-equal-to-sign with field compare when less (should fail) + EXPECTED +00000000475.0630000, WAS +00000000475.0620000 + PASS: 51. Display Numeric field equals literal + + 51 TEST CASES WERE EXECUTED + 26 PASSED + 25 FAILED +================================================= +TESTSUITE: +Tests of alphanumeric expectations + PASS: 1. Equality with an alphanumeric literal using TO BE + PASS: 2. Equality with an alphanumeric literal using TO EQUAL + PASS: 3. Equality with an alphanumeric literal using '=' + PASS: 4. Equality with an alphanumeric literal and reference modification + PASS: 5. Non-equality with an alphanumeric literal using TO BE + PASS: 6. Non-equality with an alphanumeric literal using TO EQUAL + PASS: 7. Non-equality with an alphanumeric literal using '!=' + PASS: 8. Non-equality with an alphanumeric literal and reference modification + PASS: 9. Greater-than sign with an alphanumeric literal + PASS: 10. Less-than sign with an alphanumeric literal + PASS: 11. Not greater-than sign with an alphanumeric literal + PASS: 12. Not less-than sign with an alphanumeric literal + PASS: 13. Display numeric + + 13 TEST CASES WERE EXECUTED + 13 PASSED + 0 FAILED +================================================= +TESTSUITE: +Greeting includes the user name when it is provided +**** FAIL: 1. When message type is greeting it returns Hello, James! + EXPECTED , WAS +**** FAIL: 2. When message type is farewell it returns Goodbye, James! + EXPECTED , WAS + PASS: 3. User name for greeting and farewell are consistent +TESTSUITE: +Greeting returns the appropriate message based on message type +**** FAIL: 4. When message type is greeting it returns 'Hello, World!' + EXPECTED , WAS + PASS: 5. try numerical compare +**** FAIL: 6. try 88 level compare + EXPECTED , WAS +**** FAIL: 7. When message type is farewell it returns See you later, alligator! + EXPECTED , WAS +**** FAIL: 8. Message type greeting is not true + EXPECTED , WAS + + 8 TEST CASES WERE EXECUTED + 2 PASSED + 6 FAILED +================================================= +TESTSUITE: +Tests for a sequential file copy program + PASS: 1. Output fields are populated from the input record + PASS: 2. Output fields are populated from the input record + + 2 TEST CASES WERE EXECUTED + 2 PASSED + 0 FAILED +================================================= +TESTSUITE: +Before and after tests 1 + PASS: 1. Before sets value + PASS: 2. After sets value + PASS: 3. Before happens before Testcase + PASS: 4. After happens after Testcase (part 1) + PASS: 5. After happens after Testcase (part 2) +TESTSUITE: +Before and after tests 2 + PASS: 6. VERIFY EXACT 0 ACCESSES TO SECTION 000-START + PASS: 7. VERIFY EXACT 0 ACCESSES TO SECTION 100-WELCOME + PASS: 8. Only global mocks apply in before/after (part 1) + PASS: 9. Only global mocks apply in before/after (part 1) + PASS: 10. Only global mocks apply in before/after (part 1) + PASS: 11. Only global mocks apply in before/after (part 2) + PASS: 12. Only global mocks apply in before/after (part 2) + PASS: 13. Only global mocks apply in before/after (part 2) + PASS: 14. Only global mocks apply in before/after (part 3) + PASS: 15. Only global mocks apply in before/after (part 3) + PASS: 16. Only global mocks apply in before/after (part 3) +TESTSUITE: +Before and after tests 3 + PASS: 17. Before proceeds after from last testcase (part 1) + PASS: 18. Before proceeds after from last testcase (part 2) +TESTSUITE: +General tests + PASS: 19. Welcome section performs as intended + PASS: 20. Welcome section performs as intended + PASS: 21. Goodbye section performs as intended + PASS: 22. Goodbye section performs as intended + PASS: 23. Change-1 changes hello to bye + PASS: 24. Change-1 changes hello to bye + PASS: 25. Change-1 changes bye to hello + PASS: 26. Change-1 changes bye to hello + PASS: 27. Change-2 changes hi to see you + PASS: 28. Change-2 changes hi to see you + PASS: 29. Change-2 changes see you to hi + PASS: 30. Change-2 changes see you to hi + PASS: 31. Switches values + PASS: 32. Switches values +TESTSUITE: +Mock Call statements test + PASS: 33. Simple call mock works + PASS: 34. Simple call mock works + PASS: 35. VERIFY EXACT 1 ACCESS TO CALL 'PROG1' + PASS: 36. Simple global call mock works + PASS: 37. Simple global call mock works + PASS: 38. VERIFY EXACT 1 ACCESS TO CALL 'PROG1' + PASS: 39. Call mock with argument works + PASS: 40. Call mock with argument works + PASS: 41. VERIFY EXACT 1 ACCESS TO CALL VALUE-2 + PASS: 42. Call mock with content reference for arguments work + PASS: 43. Call mock with content reference for arguments work + PASS: 44. Call mock with content reference for arguments work + PASS: 45. VERIFY EXACT 2 ACCESSES TO CALL 'PROG3' + PASS: 46. Paragraph mock is called and call mock is ignored + PASS: 47. Paragraph mock is called and call mock is ignored + PASS: 48. Paragraph mock is called and call mock is ignored + PASS: 49. VERIFY EXACT 1 ACCESS TO PARAGRAPH 800-MAKE-CALL + PASS: 50. VERIFY EXACT 0 ACCESSES TO CALL 'PROG3' +**** FAIL: 51. Global call mock is not overwritten by local call mock (Should fail) + EXPECTED , WAS +TESTSUITE: +Mock Sections And Paragraphs + PASS: 52. Global mock behaves as intended + PASS: 53. Global mock behaves as intended + PASS: 54. VERIFY EXACT 1 ACCESS TO SECTION 000-START + PASS: 55. Local mock overwrites global mock + PASS: 56. Local mock overwrites global mock + PASS: 57. VERIFY AT LEAST 2 ACCESSES TO SECTION 000-START + PASS: 58. Multiple local mocks behaves as intended (First Verify should fail) + PASS: 59. Multiple local mocks behaves as intended (First Verify should fail) +**** FAIL: 60. VERIFY ACCESSES TO SECTION 000-START + EXPECTED EXACT 5 ACCESSES, WAS 2 + PASS: 61. VERIFY NO MORE THAN 10 ACCESSES TO SECTION 100-WELCOME + PASS: 62. VERIFY EXACT 0 ACCESSES TO PARAGRAPH 500-SWITCH + PASS: 63. Empty local mock makes section do nothing + PASS: 64. Empty local mock makes section do nothing + PASS: 65. VERIFY EXACT 1 ACCESS TO PARAGRAPH 500-SWITCH + PASS: 66. Local and global mocks can be used together + PASS: 67. Local and global mocks can be used together + PASS: 68. If no local or global mock run source code + PASS: 69. If no local or global mock run source code + PASS: 70. Global paragraph mock works + PASS: 71. Global paragraph mock works + + 71 TEST CASES WERE EXECUTED + 69 PASSED + 2 FAILED +================================================= +TESTSUITE: +Verify Cobol Check handles decimal is comma properly + PASS: 1. simple expect for number + PASS: 2. simple expect for number + PASS: 3. VERIFY EXACT 1 ACCESS TO PARAGRAPH 100-ASSIGN + + 3 TEST CASES WERE EXECUTED + 3 PASSED + 0 FAILED +================================================= \ No newline at end of file diff --git a/src/test/approvalTest/readme.md b/src/test/approvalTest/readme.md new file mode 100644 index 00000000..5f0e7a3d --- /dev/null +++ b/src/test/approvalTest/readme.md @@ -0,0 +1,59 @@ +# Approval Testing Documentation +version 0.9 - not final yet... +To use this setup, the verifyAction must be updated. + +## Approval Test Flow +Approval testing verifies that the output of code matches an approved reference. The typical flow is: + +1. Run the approval tests. +2. The test generates and output file based on the current code. +3. The output is compared to a previously approved file. +4. If the output matches, the test passes. +5. If there are differences, the test fails and highlights the changes. +6. Review the differences. If correct, approve the new output by replacing the approved file. +7. Commit the updated approved files to version control. + +This process helps ensure that changes to code are intentional and reviewed. + +## Gradle Task and Used Files +The approval tests are run using a custom Gradle task defined in build.gradle. +This task executes the approval test script, which generates output files for comparison. + +**Key files:** +- ```build.gradle```: Contains the configuration and task for running approval tests. +- ```src/test/approvalTest/approvaltest```: The main script for running approval tests (use ```approvaltestWin.cmd``` on Windows). +- ```src/test/approvalTest/received/```: Directory where test output files are generated. +- ```src/test/approvalTest/approved/```: Directory containing the approved reference files. +- ```src/test/approvalTest/readme.md```: Documentation for the approval test flow. + +The Gradle task automates running the approval test script and manages the comparison between received and approved files. +If differences are found, you can review and update the approved files as needed. + +## Workings +1. Developer maintains the code and the approved file that resides under ```src/test/approvalTest/```. +2. When (the developer runs gradle or) the Gradle task executes, files are copied to ```build/approvalTest/```. During this process, the jar version is injected into the script. +3. The script generates a new output file in the ```build/approvalTest/``` directory. + +## Script connections +### Windows + +```mermaid +graph LR; +A[build.gradle.task.approvalTest] --> B[approvaltestWin.cmd] +B --> C[cobolcheck.cmd] +C --> D[cobolcheck.jar] +``` + +### Linux +```mermaid +graph LR; + A[build.gradle.task.approvalTest]-->B[approvaltest.cmd]; + B-->C[cobolcheck]; + C-->D[cobolcheck.jar]; +``` + +## To do +- [ ] Make it clear what tests are to be performed. developer must be able to remove/add tests easily +- [ ] Split the cobol-check script into smaller parts to handle one test at a time and compare results 1:1 +- [ ] Improve the script to handle more dynamic scenarios for the developer +- [ ] Finalize version 1.0 diff --git a/src/test/approvalTest/templates/cobolcheck b/src/test/approvalTest/templates/cobolcheck new file mode 100644 index 00000000..304f8c39 --- /dev/null +++ b/src/test/approvalTest/templates/cobolcheck @@ -0,0 +1,2 @@ +#!/bin/sh +java -jar bin/cobol-check-@VERSION@.jar $@ \ No newline at end of file diff --git a/src/test/approvalTest/templates/cobolcheck.cmd b/src/test/approvalTest/templates/cobolcheck.cmd new file mode 100644 index 00000000..7da8e498 --- /dev/null +++ b/src/test/approvalTest/templates/cobolcheck.cmd @@ -0,0 +1,2 @@ +@echo off +java -jar bin\cobol-check-@VERSION@.jar %* \ No newline at end of file diff --git a/src/test/cobol/FDTEST/FDTEST01.CUT b/src/test/cobol/FDTEST/FDTEST01.CUT new file mode 100644 index 00000000..8bb7739c --- /dev/null +++ b/src/test/cobol/FDTEST/FDTEST01.CUT @@ -0,0 +1,27 @@ + TestSuite "Verify FILE SECTION variables are handled properly" + + TestCase "simple expect for number" + Move 12 to WS-A-VAR-2 + Expect WS-A-VAR-2 to equal 12 + + TestCase "simple expect for string" + Move "H" to WS-A-VAR-1 + Expect WS-A-VAR-1 to equal "H" + + TestCase "simple expect for 88 level" + move 42 to WS-A-VAR-2 + Expect SW-VAR-2-IS-42 to be true + + TestCase "simple expect for 88 level false" + move 43 to WS-A-VAR-2 + Expect SW-VAR-2-IS-42 to be false + + + TestCase "expect for 88 on two lines" + move 'HELLO' to WS-A-VAR-3 + Expect SW-VAR-3-IS-HELLO to be true + + move 'WORLD' to WS-A-VAR-3 + Expect SW-VAR-3-IS-HELLO to be false + Expect SW-VAR-3-IS-WORLD to be true + diff --git a/src/test/cobol/MOCKTEST/BeforeAfterTest.cut b/src/test/cobol/MOCKTEST/BeforeAfterTest.cut index 1218d33c..9d180be9 100644 --- a/src/test/cobol/MOCKTEST/BeforeAfterTest.cut +++ b/src/test/cobol/MOCKTEST/BeforeAfterTest.cut @@ -1,4 +1,4 @@ - TestSuite "Before and after tests 1" +TestSuite "Suite A: Before and after tests 1" BEFORE-EACH MOVE "prepare" TO VALUE-1 @@ -8,25 +8,26 @@ MOVE "clean" TO VALUE-2 END-AFTER - TestCase "Before sets value" + TestCase "A-1: BEFORE-EACH sets value" EXPECT VALUE-1 TO BE "prepare" + * validate the initial state of VALUE-2, before AFTER-EACH runs and is validated in the next test + EXPECT VALUE-2 TO BE "initial" - TestCase "After sets value" + TestCase "A-2: AFTER-EACH sets value" EXPECT VALUE-2 TO BE "clean" - TestCase "Before happens before Testcase" + TestCase "A-3: Before happens before Testcase" MOVE "during" TO VALUE-1 EXPECT VALUE-1 TO BE "during" - TestCase "After happens after Testcase (part 1)" + TestCase "A-4: After happens after Testcase (part 1)" MOVE "during" TO VALUE-2 EXPECT VALUE-2 TO BE "during" - TestCase "After happens after Testcase (part 2)" + TestCase "A-5: After happens after Testcase (part 2)" EXPECT VALUE-2 TO BE "clean" - - TestSuite "Before and after tests 2" +TestSuite "Suite A2: Before and after tests 2" BEFORE-EACH MOVE "before1" TO VALUE-1 MOVE "before2" TO VALUE-2 @@ -47,11 +48,11 @@ MOVE "GlobalMockedSection2" TO VALUE-3 END-MOCK - TestCase "Performs of mocked items does not influence count" + TestCase "A2-1: Performs of mocked items does not influence count" VERIFY SECTION 000-START HAPPENED 0 TIMES VERIFY SECTION 100-WELCOME NEVER HAPPENED - TestCase "Only global mocks apply in before/after (part 1)" + TestCase "A2-2: Only global mocks apply in before/after (part 1)" MOVE "arg3" TO VALUE-3 MOCK SECTION 000-START MOVE "LocalMockedSection1" TO VALUE-1 @@ -63,7 +64,7 @@ EXPECT VALUE-2 TO BE "before2" EXPECT VALUE-3 TO BE "arg3" - TestCase "Only global mocks apply in before/after (part 2)" + TestCase "A2-3: Only global mocks apply in before/after (part 2)" MOCK SECTION 000-START MOVE "LocalMockedSection1" TO VALUE-1 END-MOCK @@ -74,7 +75,7 @@ EXPECT VALUE-2 TO BE "before2" EXPECT VALUE-3 TO BE "GlobalMockedSection2" - TestCase "Only global mocks apply in before/after (part 3)" + TestCase "A2-4: Only global mocks apply in before/after (part 3)" MOCK SECTION 000-START MOVE "LocalMockedSection1" TO VALUE-1 END-MOCK @@ -85,7 +86,7 @@ EXPECT VALUE-2 TO BE "before2" EXPECT VALUE-3 TO BE "GlobalMockedSection2" - TestSuite "Before and after tests 3" +TestSuite "Suite A3: Before and after tests 3" BEFORE EACH MOVE "before" TO VALUE-1 @@ -95,8 +96,9 @@ MOVE "after" TO VALUE-1 END-AFTER - TestCase "Before proceeds after from last testcase (part 1)" + TestCase "A3-1: Before proceeds after from last testcase (part 1)" EXPECT VALUE-1 TO BE "before" - TestCase "Before proceeds after from last testcase (part 2)" - EXPECT VALUE-1 TO BE "before" \ No newline at end of file + TestCase "A3-2: Before proceeds after from last testcase (part 2)" + EXPECT VALUE-1 TO BE "before" + . \ No newline at end of file diff --git a/src/test/cobol/MOCKTEST/GeneralTest.cut b/src/test/cobol/MOCKTEST/GeneralTest.cut index 4349a3e5..754c0a15 100644 --- a/src/test/cobol/MOCKTEST/GeneralTest.cut +++ b/src/test/cobol/MOCKTEST/GeneralTest.cut @@ -1,44 +1,45 @@ - TestSuite "General tests" +TestSuite "Suite B: General tests" - TestCase "No Expect or Verify" + TestCase "B-1: No Expect or Verify" PERFORM 100-WELCOME - TestCase "Welcome section performs as intended" + TestCase "B-2: Welcome section performs as intended" PERFORM 100-WELCOME Expect VALUE-1 to be "Hello" Expect VALUE-2 to be "Hi" - TestCase "Goodbye section performs as intended" + TestCase "B-3: Goodbye section performs as intended" PERFORM 200-GOODBYE Expect VALUE-1 to be "Bye" Expect VALUE-2 to be "See you" - TestCase "Change-1 changes hello to bye" + TestCase "B-4: Change-1 changes hello to bye" PERFORM 100-WELCOME PERFORM 300-CHANGE-1 Expect VALUE-1 to be "Bye" Expect VALUE-2 to be "Hi" - TestCase "Change-1 changes bye to hello" + TestCase "B-5: Change-1 changes bye to hello" PERFORM 200-GOODBYE PERFORM 300-CHANGE-1 Expect VALUE-1 to be "Hello" Expect VALUE-2 to be "See you" - TestCase "Change-2 changes hi to see you" + TestCase "B-6: Change-2 changes hi to see you" PERFORM 100-WELCOME PERFORM 400-CHANGE-2 Expect VALUE-1 to be "Hello" Expect VALUE-2 to be "See you" - TestCase "Change-2 changes see you to hi" + TestCase "B-7: Change-2 changes see you to hi" PERFORM 200-GOODBYE PERFORM 400-CHANGE-2 Expect VALUE-1 to be "Bye" Expect VALUE-2 to be "Hi" - TestCase "Switches values" + TestCase "B-8: Switches values" PERFORM 100-WELCOME PERFORM 500-SWITCH Expect VALUE-1 to be "Hi" Expect VALUE-2 to be "Hello" + . \ No newline at end of file diff --git a/src/test/cobol/MOCKTEST/MockCallTest.cut b/src/test/cobol/MOCKTEST/MockCallTest.cut index 9022b2b3..5c4943f7 100644 --- a/src/test/cobol/MOCKTEST/MockCallTest.cut +++ b/src/test/cobol/MOCKTEST/MockCallTest.cut @@ -1,15 +1,16 @@ - * Test for mocking call statements +* Test for mocking call statements * Writing a comment to test stuff * *Yeah * - TestSuite "Mock Call statements test" + TestSuite "Suite C: Mock Call statements test" MOCK CALL 'PROG1' MOVE "Global PROG1" TO VALUE-1 END-MOCK + - TestCase "Simple call mock works" + TestCase "C-1 Simple call mock works" MOCK CALL 'PROG1' MOVE "From mocked PROG1" TO VALUE-1 END-MOCK @@ -18,13 +19,13 @@ EXPECT VALUE-2 TO BE "arg2" VERIFY CALL 'PROG1' HAPPENED ONCE - TestCase "Simple global call mock works" + TestCase "C-2 Simple global call mock works" PERFORM 600-MAKE-CALL EXPECT VALUE-1 TO BE "Global PROG1" EXPECT VALUE-2 TO BE "arg2" VERIFY CALL 'PROG1' HAPPENED ONCE - TestCase "Call mock with argument works" + TestCase "C-3 Call mock with argument works" MOCK CALL VALUE-2 USING VALUE-1 MOVE "From mocked PROG2" TO VALUE-1 END-MOCK @@ -34,7 +35,7 @@ VERIFY CALL VALUE-2 USING VALUE-1 HAPPENED ONCE - TestCase "Call mock with field and no arguments work" + TestCase "C-4 Call mock with field and no arguments work" MOCK CALL VALUE-2 MOVE "From mocked PROG2" TO VALUE-1 END-MOCK @@ -49,7 +50,7 @@ * *Yeah * - TestCase "Call mock with content reference for arguments work" + TestCase "C-5 Call mock with content reference for arguments work" MOCK CALL 'PROG3' USING BY CONTENT VALUE-1, BY VALUE VALUE-2, @@ -68,7 +69,7 @@ VALUE-3 HAPPENED 2 TIMES - TestCase "Call mock with content reference for arguments with one comma work" + TestCase "C-6 Call mock with content reference for arguments with one comma work" MOCK CALL 'PROG3' USING BY CONTENT VALUE-1, BY VALUE VALUE-2 @@ -88,7 +89,7 @@ HAPPENED 2 TIMES - TestCase "Call mock with content reference for arguments without comma work" + TestCase "C-7 Call mock with content reference for arguments without comma work" MOCK CALL 'PROG3' USING BY CONTENT VALUE-1 BY VALUE VALUE-2 @@ -108,7 +109,7 @@ HAPPENED 2 TIMES - TestCase "Paragraph mock is called and call mock is ignored" + TestCase "C-8 Paragraph mock is called and call mock is ignored" MOCK CALL 'PROG3' USING BY CONTENT VALUE-1, BY VALUE VALUE-2, @@ -133,14 +134,14 @@ VALUE-3 NEVER HAPPENED - TestCase "Global call mock is not overwritten by local call mock (Should fail)" + TestCase "C-9 Global call mock is overwritten by local call mock" MOCK CALL 'PROG1' MOVE "Local PROG1" TO VALUE-1 END-MOCK PERFORM 600-MAKE-CALL - EXPECT VALUE-1 TO BE "Global PROG1" + EXPECT VALUE-1 TO BE "Local PROG1" - TestCase "Mock of call with exception handling works" + TestCase "C-10 Mock of call with exception handling works" MOCK CALL 'PROGRAM' USING VALUE-1 MOVE "worked" TO VALUE-1 @@ -149,7 +150,7 @@ PERFORM 900-MAKE-CALL EXPECT VALUE-1 TO BE "worked" - TestCase "Mock of call with exception handling ending with period works" + TestCase "C-11 Mock of call with exception handling ending with period works" MOCK CALL 'PROGRAM' USING VALUE-1 MOVE "worked" TO VALUE-1 END-MOCK @@ -157,7 +158,7 @@ PERFORM 1000-MAKE-CALL EXPECT VALUE-1 TO BE "worked" - TestCase "Mock of call with a new call as exception handling works" + TestCase "C-12 Mock of call with a new call as exception handling works" MOCK CALL 'PROGRAM' USING VALUE-1 MOVE "worked" TO VALUE-1 END-MOCK @@ -165,7 +166,7 @@ PERFORM 1100-MAKE-CALL EXPECT VALUE-1 TO BE "worked" - TestCase "Mock of call with a new call as exception handling ending with period works" + TestCase "C-13 Mock of call with a new call as exception handling ending with period works" MOCK CALL 'PROGRAM' USING VALUE-1 MOVE "worked" TO VALUE-1 END-MOCK @@ -178,7 +179,7 @@ VERIFY CALL 'PROGRAM' USING VALUE-1 HAPPENED ONCE VERIFY CALL 'PROGRAM2' USING VALUE-1 NEVER HAPPENED - TestCase "Mock of calls in series with exception handling works" + TestCase "C-14 Mock of calls in series with exception handling works" MOCK CALL 'PROGRAM' USING VALUE-1 MOVE "worked" TO VALUE-1 END-MOCK @@ -187,12 +188,13 @@ EXPECT VALUE-1 TO BE "worked" VERIFY CALL 'PROGRAM' USING VALUE-1 HAPPENED 2 TIMES - TestCase "CALL MOCK using parameters with IN are recognized correctly" + TestCase "C-15 CALL MOCK using parameters with IN are recognized correctly" MOVE SPACES TO OUTPUT-VALUE - MOCK CALL 'MYCOBOL' USING ACTION-PARAM, - BOOK-PARAM in COBOL-STRUCTURE, - OUTPUT-PARAM. + MOCK CALL 'MYCOBOL' USING ACTION-value, + BOOK-value in book-STRUCTURE, + OUTPUT-value. MOVE "MOCKED" TO OUTPUT-VALUE END-MOCK - PERFORM 610-MAKE-CALL-STRUCTURE - EXPECT OUTPUT-VALUE TO BE "MOCKED" \ No newline at end of file + PERFORM 610-CALL-VALUE-IN-STRUCTURE + EXPECT OUTPUT-VALUE TO BE "MOCKED" + . \ No newline at end of file diff --git a/src/test/cobol/MOCKTEST/MockSectionsAndParagraphsTest.cut b/src/test/cobol/MOCKTEST/MockSectionsAndParagraphsTest.cut index a5695760..efca67ca 100644 --- a/src/test/cobol/MOCKTEST/MockSectionsAndParagraphsTest.cut +++ b/src/test/cobol/MOCKTEST/MockSectionsAndParagraphsTest.cut @@ -1,4 +1,4 @@ - TestSuite "Mock Sections And Paragraphs" + TestSuite "Suite D: Mock Sections And Paragraphs" MOCK SECTION 000-START PERFORM 100-WELCOME @@ -14,14 +14,14 @@ END-EVALUATE END-MOCK - TestCase "Global mock behaves as intended" + TestCase "D-1: Global mock behaves as intended" PERFORM 000-START Expect VALUE-1 to be "Hi" Expect VALUE-2 to be "Hello" VERIFY SECTION 000-START HAPPENED ONCE VERIFY PARAGRAPH 300-CHANGE-1 HAPPENED ZERO TIMES - TestCase "Local mock overwrites global mock" + TestCase "D-2: Local mock overwrites global mock" MOCK SECTION 000-START MOVE "This is" TO VALUE-1 MOVE "a mock" TO VALUE-2 @@ -32,7 +32,7 @@ Expect VALUE-2 to be "a mock" VERIFY SECTION 000-START HAPPENED AT LEAST 2 TIMES - TestCase "Multiple local mocks behaves as intended (First Verify should fail)" + TestCase "D-3: Multiple local mocks behaves as intended" MOCK SECTION 000-START MOVE "This is" TO VALUE-1 MOVE "a mock" TO VALUE-2 @@ -43,17 +43,19 @@ MOCK SECTION 100-WELCOME MOVE "I'll leave VALUE-2 alone" TO VALUE-1 END-MOCK - MOCK PARAGRAPH 500-SWITCH - END-MOCK + + MOCK PARAGRAPH 500-SWITCH END-MOCK + PERFORM 000-START PERFORM 000-START - VERIFY SECTION 000-START HAPPENED 5 TIMES + + VERIFY SECTION 000-START HAPPENED 2 TIMES Expect VALUE-1 to be "I'll leave VALUE-2 alone" - VERIFY SECTION 100-WELCOME HAPPENED NO MORE THAN 10 TIMES + VERIFY SECTION 100-WELCOME HAPPENED 6 TIMES VERIFY PARAGRAPH 500-SWITCH NEVER HAPPENED Expect VALUE-2 to be "a mock" - TestCase "Empty local mock makes section do nothing" + TestCase "D-4: Empty local mock makes section do nothing" MOCK PARAGRAPH 500-SWITCH END-MOCK PERFORM 100-WELCOME @@ -62,7 +64,7 @@ Expect VALUE-2 to be "Hi" VERIFY PARAGRAPH 500-SWITCH HAPPENED ONCE - TestCase "Local and global mocks can be used together" + TestCase "D-5: Local and global mocks can be used together" MOCK SECTION 100-WELCOME MOVE "Mockity-" TO VALUE-1 MOVE "Mock" TO VALUE-2 @@ -71,42 +73,42 @@ Expect VALUE-1 to be "Mock" Expect VALUE-2 to be "Mockity-" - TestCase "If no local or global mock run source code" + TestCase "D-6: If no local or global mock run source code" PERFORM 100-WELCOME Expect VALUE-1 to be "Hello" Expect VALUE-2 to be "Hi" - TestCase "Global paragraph mock works" + TestCase "D-7: Global paragraph mock works" PERFORM 100-WELCOME PERFORM 300-CHANGE-1 Expect VALUE-1 to be "MOCKED" Expect VALUE-2 to be "Hi" - TestCase "Mock with EXIT SECTION in source works" + TestCase "D-8: Mock with EXIT SECTION in source works" MOCK SECTION 400-CHANGE-2 MOVE "Exit section is handled correctly" TO VALUE-1 END-MOCK PERFORM 400-CHANGE-2 Expect VALUE-1 to be "Exit section is handled correctly" - TestCase "Mock one-liner with end mock on next line" + TestCase "D-9: Mock one-liner with end mock on next line" MOCK SECTION 400-CHANGE-2 MOVE "it works1" TO VALUE-1 END-MOCK PERFORM 400-CHANGE-2 Expect VALUE-1 to be "it works1" - TestCase "Mock one-liner with content" + TestCase "D-10: Mock one-liner with content" MOCK SECTION 400-CHANGE-2 MOVE "it works2" TO VALUE-1 END-MOCK PERFORM 400-CHANGE-2 Expect VALUE-1 to be "it works2" - TestCase "Mock content with end-mock on the same line" + TestCase "D-11: Mock content with end-mock on the same line" MOCK SECTION 400-CHANGE-2 MOVE "it works3" TO VALUE-1 END-MOCK PERFORM 400-CHANGE-2 Expect VALUE-1 to be "it works3" - TestCase "Mock content on multiple lines, with end-mock on the same line as some content" + TestCase "D-12: content on multiple lines, flow style" MOCK SECTION 400-CHANGE-2 MOVE "it works4" TO VALUE-1 MOVE "it works5" TO VALUE-2 END-MOCK PERFORM 400-CHANGE-2 diff --git a/src/test/cobol/REPLAC/REPLAC_01.cut b/src/test/cobol/REPLAC/REPLAC_01.cut new file mode 100644 index 00000000..75b664f4 --- /dev/null +++ b/src/test/cobol/REPLAC/REPLAC_01.cut @@ -0,0 +1,8 @@ + TESTSUITE + "Greeting includes the user name when it is provided" + + TESTCASE "When message type is greeting it returns Hello, James!" + SET MESSAGE-IS-GREETING TO TRUE + MOVE "James" TO WS-FRIEND + PERFORM 2000-SPEAK + EXPECT WS-GREETING TO BE "Hello, James!" diff --git a/src/test/cobol/WS88LEVEL/MOVE88LEVELS.CUT b/src/test/cobol/WS88LEVEL/MOVE88LEVELS.CUT new file mode 100644 index 00000000..b75f21c0 --- /dev/null +++ b/src/test/cobol/WS88LEVEL/MOVE88LEVELS.CUT @@ -0,0 +1,49 @@ +TestSuite "Suite 88 levels in program" + TestCase "t-01: WS 88 level variable on one line" + + MOVE 'A' TO WS-88-LEVELS + perform 001-INITIALIZE + expect level-88-value-1 to be true + expect level-88-value-2 to be false + + Testcase "t-02: WS 88 level variable on multiple lines" + MOVE 'B' TO WS-88-LEVELS + perform 001-INITIALIZE + expect level-88-all to be true + + TestCase "t-03: WS 88 level variable" + MOVE 4 TO WS-88-LEVELS-NUMERIC + perform 001-INITIALIZE + expect SW-88-NUMERIC-LOW to be true + expect SW-88-NUMERIC-MED-ODD to be false + expect SW-88-NUMERIC-MED-EVEN to be false + + TestCase "t-04: WS 88 level variable bigger value" + MOVE 32 TO WS-88-LEVELS-NUMERIC + perform 001-INITIALIZE + expect SW-88-NUMERIC-LOW to be false + expect SW-88-NUMERIC-MEDIUM to be true + expect SW-88-NUMERIC-MED-ODD to be false + expect SW-88-NUMERIC-MED-EVEN to be true + + TestCase "t-05: FD 88 level variable are present" + MOVE 'Z' TO FD-88-LEVELS + perform 001-INITIALIZE + expect FD-88-VALUE-3 to be false + expect FD-88-VALUE-4 to be true + expect FD-88-ALL to be true + + TestCase "t-06: FD 88 level variable numeric" + MOVE 15 TO FD-88-LEVELS-NUMERIC + perform 001-INITIALIZE + expect FD-88-NUMERIC-LOW to be false + expect FD-88-NUMERIC-MEDIUM to be true + expect FD-88-NUMERIC-HIGH to be false + + + + + + . + + diff --git a/src/test/java/org/openmainframeproject/cobolcheck/InterpreterControllerTest.java b/src/test/java/org/openmainframeproject/cobolcheck/InterpreterControllerTest.java index 20dd88ce..7ff08d45 100644 --- a/src/test/java/org/openmainframeproject/cobolcheck/InterpreterControllerTest.java +++ b/src/test/java/org/openmainframeproject/cobolcheck/InterpreterControllerTest.java @@ -9,6 +9,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import org.openmainframeproject.cobolcheck.services.cobolLogic.DataType; import java.io.BufferedReader; import java.io.IOException; @@ -744,7 +745,13 @@ public void it_updates_numeric_fields() throws IOException { while (interpreterController.interpretNextLine() != null){ interpreterController.interpretNextLine(); } - assertEquals("PACKED_DECIMAL", + + // list the statements + for (String s : interpreterController.getFileSectionStatements()){ + System.out.println("Section Statement: " + s); + } + + assertEquals("PACKED_DECIMAL", interpreterController.getNumericFieldDataTypeFor("WS-COUNT").name()); assertEquals("DISPLAY_NUMERIC", interpreterController.getNumericFieldDataTypeFor("WS-DISPLAY-NUM2").name()); @@ -986,14 +993,14 @@ public void it_registers_DB2Copybook_on_multiple_lines_as_stub() throws IOExcept boolean testsRan = false; String currentLine = ""; + String stubbedTriggerLine = ""; while (currentLine != null){ currentLine = interpreterController.interpretNextLine(); - if (currentLine != null && currentLine.contains("EXEC SQL")) { - assertTrue(interpreterController.shouldCurrentLineBeStubbed()); - testsRan = true; + if (interpreterController.shouldCurrentLineBeStubbed()) { + stubbedTriggerLine = currentLine; } } - assertTrue(testsRan); + assertNotEquals("", stubbedTriggerLine,"Stub trigger was set"); } @Test @@ -1016,4 +1023,188 @@ public void it_stubs_linkage_line() throws IOException { } assertTrue(testsRan); } + + @Test + public void it_parses_WS_section_variables_from_split_lines() throws IOException { + // debug flag, output to console if true + boolean debugOn = true; + // Store test data + ArrayList cobolLines = new ArrayList<>(Arrays.asList( + " DATA DIVISION. ", + " FILE SECTION. ", + " *-----------------------------------------------------------------", + " WORKING-STORAGE SECTION.", + " *-----------------------------------------------------------------", + " 01 WORK-FIELDS. ", + " 03 WS-PROGRAM-NAME PIC X(8) VALUE 'WSVARI'. ", + " 03 WS-NUM-9 PIC 9(9). ", + " 03 WS-NUM-S9 PIC S9(9) comp ", + " value zero. ", + " 03 WS-NUM-S9-C3 PIC S9(9) comp-3 ", + " value 1. ", + " " + )); +// Set up the mock to return each line in sequence, then null + Mockito.when(mockedReader.readLine()).thenAnswer(invocation -> { + if (!cobolLines.isEmpty()) { + String s = cobolLines.get(0); + cobolLines.remove(0); + return s; + } + return null; + }); + + while (interpreterController.interpretNextLine() != null){ + interpreterController.interpretNextLine(); + } + + if (debugOn) { + // list all the statements read from file section + System.out.println("File Section Statements:"); + for (String statement : interpreterController.getFileSectionStatements()) { + System.out.println(statement); + } + } + + assertTrue(interpreterController.isReading(Constants.WORKING_STORAGE_SECTION),"Right Section being read"); + assertEquals(3,interpreterController.getNumericFields().getNumberOfFields(),"number of variables read"); + assertEquals(DataType.DISPLAY_NUMERIC,interpreterController.getNumericFieldDataTypeFor("WS-NUM-9"),"var 'WS-NUM-9' read"); + assertEquals(DataType.BINARY,interpreterController.getNumericFieldDataTypeFor("WS-NUM-S9"),"var 'WS-NUM-S9' read"); + assertEquals(DataType.PACKED_DECIMAL,interpreterController.getNumericFieldDataTypeFor("WS-NUM-S9-C3"),"var 'WS-NUM-S9-C3' read"); + + } + // verify the variables in FILE-SECTION may be split on multiple lines + @Test + public void it_parses_file_section_variables_from_split_lines() throws IOException { + ArrayList cobolLines = new ArrayList<>(Arrays.asList( + " DATA DIVISION. ", + " FILE SECTION. ", + " *------BEFORE-----------------------------------------------------", + " FD INPUT-FILE. ", + " *------AFTER------------------------------------------------------", + " LABEL RECORD IS STANDARD ", + " RECORDING MODE F ", + " BLOCK CONTAINS 0 RECORDS ", + " DATA RECORD IS INPUT-RECORD. ", + " 01 INPUT-RECORD. ", + " 05 IN-FIELD-1 ", + " PIC X(23). ", + " 05 IN-FIELD-2 PIC X(3). ", + " " + )); + + Mockito.when(mockedReader.readLine()).thenAnswer(invocation -> { + if (!cobolLines.isEmpty()) { + return cobolLines.remove(0); + } + return null; + }); + + int i = 1; + String currentTestlineOut = ""; + while ((currentTestlineOut = interpreterController.interpretNextLine())!= null){ + + // Validate the returned line. Trimmed to avoid leading and trailing spaces are creating test issues. + // counter: i starts at 0 and counts up with each line read by the test. + // Some reads are performed inside the interpreterController so the count may not match + // the line number in the test data. + switch (i) { + case 1: + assertEquals("DATA DIVISION.", currentTestlineOut.trim(),"Line 1 read"); + break; + case 2: + assertEquals("FILE SECTION.", currentTestlineOut.trim(),"Line 2 read"); + break; + case 3: + assertEquals("*------BEFORE-----------------------------------------------------", currentTestlineOut.trim(),"Line 3 read"); + break; + case 4: + assertEquals("FD INPUT-FILE.", currentTestlineOut.trim(),"Line 4 read"); + break; + case 5: + assertEquals("*------AFTER------------------------------------------------------", currentTestlineOut.trim(),"Line 5 read"); + break; + case 6: + assertTrue(interpreterController.hasStatementBeenRead(),"we have read a statement"); + assertEquals("LABEL RECORD IS STANDARD", interpreterController.getCurrentStatement().get(0).trim(),"Line 6 read"); + assertEquals("RECORDING MODE F", interpreterController.getCurrentStatement().get(1).trim(),"Line 7 read"); + assertEquals("BLOCK CONTAINS 0 RECORDS", interpreterController.getCurrentStatement().get(2).trim(),"Line 8 read"); + assertEquals("DATA RECORD IS INPUT-RECORD.", interpreterController.getCurrentStatement().get(3).trim(),"Line 9 read"); + assertEquals("DATA RECORD IS INPUT-RECORD.", currentTestlineOut.trim(), "interpreter returns last read line after reading a multi-line statement"); + break; + case 7: + assertFalse(interpreterController.hasStatementBeenRead(),"we have not a statement"); + assertEquals("01 INPUT-RECORD.", currentTestlineOut.trim(),"Line 10 read"); + break; + case 8: + assertTrue(interpreterController.hasStatementBeenRead(),"we have read a statement"); + assertEquals("PIC X(23).", currentTestlineOut.trim(),"Line 11 read"); + assertEquals("05 IN-FIELD-1", interpreterController.getCurrentStatement().get(0).trim(),"Line 12 read"); + assertEquals("PIC X(23).", interpreterController.getCurrentStatement().get(1).trim(),"Line 13 read"); + break; + case 9: + assertFalse(interpreterController.hasStatementBeenRead(),"we have not a statement"); + assertEquals("05 IN-FIELD-2 PIC X(3).", currentTestlineOut.trim(),"Line 14 read"); + break; + case 10: + assertEquals("",currentTestlineOut.trim(),"Line 15 read, only spaces expected, dese"); + break; + default: + System.out.println("Line " + i + " read" ); + // Assert error if we get an unexpected number of lines + fail("Test data has been changed, please update the test case. Unexpected line " + i + " read."); + break; + } + i++; + } + } + + // Test the lines in the DATA DIVISION FILE SECTION are stored in the lineRepository + @Test + public void it_stores_file_section_lines_in_lineRepository() throws IOException { + ArrayList cobolLines = new ArrayList<>(Arrays.asList( + " DATA DIVISION. ", + " FILE SECTION. ", + " *------BEFORE-----------------------------------------------------", + " FD INPUT-FILE. ", + " *------AFTER------------------------------------------------------", + " LABEL RECORD IS STANDARD ", + " RECORDING MODE F ", + " BLOCK CONTAINS 0 RECORDS ", + " DATA RECORD IS INPUT-RECORD. ", + " 01 INPUT-RECORD. ", + " 05 IN-FIELD-1 ", + " PIC X(23). ", + " 05 IN-FIELD-2 PIC X(3). ", + " " + )); + + // Return one line at a time from the list, then null when the list is empty + Mockito.when(mockedReader.readLine()).thenAnswer(invocation -> { + if (!cobolLines.isEmpty()) { + return cobolLines.remove(0); + } + return null; + }); + + String currentTestlineOut = ""; + int i = 0; + while ((currentTestlineOut = interpreterController.interpretNextLine())!= null){ + i++; + } + + // Print all the lines stored in the file section for debugging + System.out.println("Lines stored in file section:"); + for (String line : interpreterController.getFileSectionStatements()) { + System.out.println(line); + } + + List fileSectionLines = interpreterController.getFileSectionStatements(); + assertEquals(4,fileSectionLines.size(),"number of lines in file section"); + assertEquals("01 INPUT-RECORD.",fileSectionLines.get(0).trim(),"first line in file section"); + assertEquals("05 IN-FIELD-1",fileSectionLines.get(1).trim(),"line in file section"); + assertEquals("PIC X(23).",fileSectionLines.get(2).trim(),"line in file section"); + assertEquals("05 IN-FIELD-2 PIC X(3).",fileSectionLines.get(3).trim(),"line in file section"); + + } } diff --git a/src/test/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpanderTest.java b/src/test/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpanderTest.java new file mode 100644 index 00000000..105ca0c3 --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/features/interpreter/CopybookExpanderTest.java @@ -0,0 +1,94 @@ +package org.openmainframeproject.cobolcheck.features.interpreter; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openmainframeproject.cobolcheck.exceptions.PossibleInternalLogicErrorException; +import org.openmainframeproject.cobolcheck.services.Config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CopybookExpanderTest { + // 1 2 3 + // 012345678901234567890123456789012 + String execSlq1 = "EXEC SQL INCLUDE TRMCKP END-EXEC."; + CopybookExpander ce; + + @BeforeAll + static void oneTimeSetup() { + Config.load("testconfig.properties"); + } + + + @BeforeEach + public void setup(){ + ce = new CopybookExpander(); + } + + @Test + public void test_find_copybook_position_start(){ + int start = ce.findCopybookNamePositionInCopyStatement(execSlq1); + assertEquals(17,start,"start found"); + } + + @Test + public void test_find_copybook_position_end() { + int endPos = ce.findCopyBookNameEndPositionInCopyStatement(execSlq1,17); + assertEquals(23,endPos,"we found the end"); + } + + @Test + public void test_we_actually_locate_the_copybook_name() { + int start = ce.findCopybookNamePositionInCopyStatement(execSlq1); + int endPos = ce.findCopyBookNameEndPositionInCopyStatement(execSlq1,start); + assertEquals("TRMCKP",execSlq1.substring(start,endPos)); + } + + @Test + public void test_extractCopybookNameFromCopyStatement(){ + assertEquals("onebookname",ce.extractCopybookNameFromCopyStatement("EXEC SQL INCLUDE onebookname END-EXEC.")); + } + + @Test + public void commentOut_adds_asterisk_at_column_7() { + String sourceLine = "123456 89"; + String result = ce.commentOut(sourceLine); + assertEquals("123456*89", result); + } + + @Test + public void commentOut_handles_short_lines_without_exception() { + String sourceLine = "12345"; + String result = ce.commentOut(sourceLine); + assertEquals("12345", result); + } + + @Test + public void commentOut_handles_empty_string() { + String sourceLine = ""; + String result = ce.commentOut(sourceLine); + assertEquals("", result); + } + + @Test + public void commentOut_handles_null_input() { + String sourceLine = null; + String result = ce.commentOut(sourceLine); + assertEquals(null, result); + } + + @Test + public void commentOut_handles_input_where_indicator_is_blocked() { + String sourceLine = "123456- 'test continuation'."; + assertThrows(PossibleInternalLogicErrorException.class, () -> ce.commentOut(sourceLine)); + } + + @Test + public void commentOut_handles_input_where_indicator_is_comment() { + String sourceLine = "123456* if more < to-to-come then"; + String result = ce.commentOut(sourceLine); + assertEquals(sourceLine, result); + } + +} diff --git a/src/test/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepositoryTest.java b/src/test/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepositoryTest.java new file mode 100644 index 00000000..45ea8403 --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/features/interpreter/LineRepositoryTest.java @@ -0,0 +1,18 @@ +package org.openmainframeproject.cobolcheck.features.interpreter; + +import org.junit.jupiter.api.Test; + +import java.awt.*; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +public class LineRepositoryTest { + // 1 2 3 + // 012345678901234567890123456789012 + // List includedLines = {"EXEC SQL INCLUDE TRMCKP END-EXEC.",""} + + + + +} diff --git a/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTest.java b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTest.java index c1968ca5..3a8ea2a2 100644 --- a/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTest.java +++ b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTest.java @@ -1,14 +1,23 @@ package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.openmainframeproject.cobolcheck.services.Config; import java.io.File; +import java.nio.file.FileSystems; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; public class ReplaceTest { + @BeforeAll + public static void setup(){ + Config.load(); + } + + @Test public void test_General_setup() { // test we can inspect multiple times and still have the correct state @@ -82,6 +91,56 @@ public void test_for_linenumber_0_every_replace_is_performed() { assertEquals(" MOVE 'Y' TO WS-EXPECTED",Replace.replace(" MOVE 'Y' TO WS-ACTUAL", 0)); } + @Test + public void test_what_happends_when_we_have_a_big_replace() { + Replace.inspectProgram(new File("./testfiles/REPLACE3.CBL")); + assertEquals(" MOVE 'B' TO REPDEMO3-PARAM",Replace.replace(" MOVE 'B' TO :PROGRAM:-PARAM", 0)); + assertEquals("123456 MOVE MY-DATA in ADVANCED-REQUEST-DATA TO UPDATE-MY-ADVANCED-REQUEST-DATA", + Replace.replace("123456 MOVE MY-DATA in ADVANCED-REQUEST-DATA TO :REQUEST:-DATA", 0)); + + } + + @Test + public void test_output_file_name_is_set_to_path_for_test_elements() { + //Config.getGeneratedTestCodePath() is where the modified files are written to + //regardless of where the input file is located + + // this test must work on all OS - Windows, Linux, Mac therefore we get the OS path Separator + String osPathSeparator = FileSystems.getDefault().getSeparator(); + + String inputFileName = "." + osPathSeparator + "testfiles" + osPathSeparator + "REPLACE.CBL"; + String expectedOutputFileName = Config.getGeneratedTestCodePath() + osPathSeparator + "REPLACE.replaced.CBL"; + assertEquals(expectedOutputFileName, Replace.getOutputFileName(inputFileName)); + + inputFileName = "." + osPathSeparator + "test" + osPathSeparator + "files" + osPathSeparator + "REPLACE.CBL"; + expectedOutputFileName = Config.getGeneratedTestCodePath() + osPathSeparator + "REPLACE.replaced.CBL"; + assertEquals(expectedOutputFileName, Replace.getOutputFileName(inputFileName)); + + inputFileName = "." + osPathSeparator + "test" + osPathSeparator + "files" + osPathSeparator + "REPLACE.xxx"; + expectedOutputFileName = Config.getGeneratedTestCodePath() + osPathSeparator + "REPLACE.replaced.xxx"; + assertEquals(expectedOutputFileName, Replace.getOutputFileName(inputFileName)); + } + + // test internal functions for filename manipulation + @Test + public void test_internal_functions_for_filename_manipulation() { + String inputName = "REPLACE.CBL"; + assertEquals("REPLACE.CBL", Replace.getFilenameWithoutPath(inputName)); + assertEquals("CBL", Replace.getFileExtension(inputName)); + assertEquals("REPLACE", Replace.getFileNameWithoutExtension(inputName)); + + // Default extension if none found + inputName = "REPLACE"; + assertEquals("CBL", Replace.getFileExtension(inputName)); + } + + @Test + public void test_internal_functions_for_filename_manipulation_path() { + String input = "./test/files/REPLACE.CBL"; + assertEquals("REPLACE.CBL", Replace.getFilenameWithoutPath(input)); + } + + diff --git a/testfiles/REPLACE3.CBL b/testfiles/REPLACE3.CBL new file mode 100644 index 00000000..31ac2fdf --- /dev/null +++ b/testfiles/REPLACE3.CBL @@ -0,0 +1,49 @@ + ********************************************************************** + * AUTHOR: T. N. KRAMER + * DATE: 25 APR 2025 + * PURPOSE: DEMONSTRATE REPLACE STATEMENT + ********************************************************************** + IDENTIFICATION DIVISION. + PROGRAM-ID. REPDEMO3. + ENVIRONMENT DIVISION. + DATA DIVISION. + WORKING-STORAGE SECTION. + 77 WS-OMEGA PIC X. + 77 WS-GAMMA PIC X. + 77 UT-OMEGA PIC X. + 77 UT-GAMMA PIC X. + + + REPLACE ==:PROGRAM:== BY ==REPDEMO3== + ==:REQUEST:== BY ==UPDATE-MY-ADVANCED-REQUEST== + ==:RESPONSE:== BY ==UPDATE-MY-ADVANCED-RESPONSE==. + + PROCEDURE DIVISION. + + SET UT-COMPARE-DEFAULT TO TRUE + PERFORM UT-ASSERT-EQUAL + ADD 1 TO UT-TEST-CASE-COUNT + SET UT-NORMAL-COMPARE TO TRUE + MOVE WS-OMEGA TO UT-ACTUAL + MOVE 'Y' TO UT-EXPECTED + . + 3000-DYNAMIC-CALL. + MOVE 'A' TO WS-ALPHA + MOVE 'Z' TO WS-OMEGA + * CALL WS-SUBPROGRAM-NAME + * USING WS-ALPHA WS-OMEGA + MOVE "B" TO WS-ALPHA + MOVE "Y" TO WS-OMEGA + . + + 3001-DYNAMIC-CALL + MOVE 'A' TO WS-ALPHA + MOVE 'Z' TO WS-OMEGA + * CALL WS-SUBPROGRAM-NAME + * USING WS-ALPHA WS-OMEGA + MOVE "B" TO WS-ALPHA + MOVE "Y" TO WS-OMEGA + . + + 9999-END. + . \ No newline at end of file diff --git a/vs-code-extension/snippets/cut-snippets.json b/vs-code-extension/snippets/cut-snippets.json index db651fd3..9d27ca5c 100644 --- a/vs-code-extension/snippets/cut-snippets.json +++ b/vs-code-extension/snippets/cut-snippets.json @@ -1,7 +1,7 @@ { "AFTER EACH Block": { "prefix": "AFTER EACH", - "description": "Instantiates an AFTER EACH block and places the curso in its body", + "description": "Instantiates an AFTER EACH block and places the cursor in its body", "body": [ "AFTER EACH", " ${0}",