diff --git a/CHANGELOG.md b/CHANGELOG.md index cac223ac..3449771e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Mock SQL tables - Mock batch file I/O + + +## \[0.2.16\] 2024-12-05 +- Fixed a bug an extra space would be added to the list of mocks, which would create issues when looking for mocks. +- Added support for the replace statement in unit test source + ## \[0.2.14\] 2024-12-05 - Fixed a bug where an exit code of 4 was not allowed. diff --git a/README.md b/README.md index 675646ca..c24867cb 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ As of March 2022 we could use help with: ## Downloads -Version 0.2.14 pre-release is available! +Version 0.2.16 pre-release is available! [//]: # (- Find the download on the project home page on the [Neo Pragma site](https://neopragma.com/projects/cobol-check/).) - Find distributions here: [Cobol Check Ditributions](https://github.com/openmainframeproject/cobol-check/tree/Developer/build/distributions). diff --git a/approvaltestWin.cmd b/approvaltestWin.cmd index 37fb6766..22fba940 100644 --- a/approvaltestWin.cmd +++ b/approvaltestWin.cmd @@ -1 +1 @@ -./cobolcheck -p NUMBERS ALPHA GREETING FILECOPY MOCKTEST DPICNUMBERS > approval-test-actual.txt \ No newline at end of file +./cobolcheck -p ALPHA DB2PROG DPICNUMBERS FILECOPY GREETING MOCK MOCKPARA MOCKTEST NUMBERS RETURNCODE TESTNESTED > approval-test-actual.txt \ No newline at end of file diff --git a/build.gradle b/build.gradle index 68efef26..c898ec02 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ plugins { id 'jacoco' } -def productVersion = '0.2.14' +def productVersion = '0.2.16' def productName = 'cobol-check' group = 'org.openmainframeproject' description = 'Unit testing framework for Cobol' diff --git a/build/distributions/cobol-check-0.2.15.zip b/build/distributions/cobol-check-0.2.15.zip new file mode 100644 index 00000000..5581678b Binary files /dev/null and b/build/distributions/cobol-check-0.2.15.zip differ diff --git a/build/distributions/cobol-check-0.2.16.zip b/build/distributions/cobol-check-0.2.16.zip new file mode 100644 index 00000000..d6bf2d30 Binary files /dev/null and b/build/distributions/cobol-check-0.2.16.zip differ diff --git a/src/main/cobol/BIPM012.CBL b/src/main/cobol/BIPM012.CBL new file mode 100644 index 00000000..89ea481a --- /dev/null +++ b/src/main/cobol/BIPM012.CBL @@ -0,0 +1,48 @@ + ID DIVISION. + PROGRAM-ID. BIPM012. + AUTHOR. (TNP). + DATE-WRITTEN. 20.01.2025. + *--------------+------+------------------------------------------- + * + * FUNKTION UNITTEST VALIDATION - REPLACE + * + *--------------+------+------------------------------------------- + ENVIRONMENT DIVISION. + CONFIGURATION SECTION. + SPECIAL-NAMES. + DECIMAL-POINT IS COMMA. + INPUT-OUTPUT SECTION. + FILE-CONTROL. + DATA DIVISION. + FILE SECTION. + + WORKING-STORAGE SECTION. + *----------------------------------------------------------------- + 01 WORK-FIELDS-1. + 03 MOVE-IDX PIC S9(4) COMP. + 03 WS-BALANCE-X PIC S9(11)V9(2) VALUE 0 COMP. + 03 WS-NUMBER-OF-MOVE PIC S9(07) COMP-3. + + 03 WS-DATE-BEFORE PIC X(10). + 03 WS-DATE-AFTER PIC X(10). + *----------------------------------------------------------------- + 01 BIPM012-PARM. + 07 INPUT-DATA. + 10 USERNO PIC 9(03). + 07 OUTPUT-DATA. + 10 MAX-HEIGHT usage COMP-3 PIC S9(5). + 10 MAX-LENGTH PIC S9(07) usage COMP-3. + + *----------------------------------------------------------------- + + REPLACE ==:BDSIXXX:== BY ==BIPM012==. + + *----------------------------------------------------------------- + PROCEDURE DIVISION. + *----------------------------------------------------------------- + MOVE ZERO TO MAX-LENGTH IN :BDSIXXX:-PARM + . + 100-MOVE-DATA SECTION. + *----------------------------------------------------------------- + MOVE WS-DATE-BEFORE TO WS-DATE-AFTER + . \ No newline at end of file diff --git a/src/main/cobol/MOCK.CBL b/src/main/cobol/MOCK.CBL index 58a3d2e3..783ce389 100644 --- a/src/main/cobol/MOCK.CBL +++ b/src/main/cobol/MOCK.CBL @@ -106,6 +106,17 @@ MOVE output-value TO VALUE-1 . + 611-CALL-WITH-STRUCTURE. + MOVE "1" to ACTION-PARAM + MOVE "2" to BOOK-PARAM + MOVE "3" to OUTPUT-VALUE + CALL 'MYCOBOL' USING ACTION-PARAM, + BOOK-PARAM IN COBOL-STRUCTURE, + OUTPUT-VALUE IN COBOL-STRUCTURE + END-CALL + MOVE OUTPUT-VALUE IN COBOL-STRUCTURE TO VALUE-1 + . + 700-MAKE-CALL. MOVE "arg1" to VALUE-1 MOVE "arg2" to VALUE-2 diff --git a/src/main/cobol/copy/BIPM012I.CBL b/src/main/cobol/copy/BIPM012I.CBL new file mode 100644 index 00000000..8b7686de --- /dev/null +++ b/src/main/cobol/copy/BIPM012I.CBL @@ -0,0 +1,14 @@ + *----------------------------------------------------------------- + * >>> START COPYBOOK BIPM012I + * + *----------------------------------------------------------------- + 04 MODULE-DATA. + 05 IO-TABLE. + 07 INPUT-DATA. + 09 USERNO PIC 9(03). + 07 OUTPUT-DATA. + 10 MAX-HEIGHT COMP-3 PIC S9(5). + 10 MAX-LENGTH PIC S9(07) COMP-3. + *----------------------------------------------------------------- + * <<< END COPYBOOK BIPM012I + *----------------------------------------------------------------- \ No newline at end of file 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 40a79c30..0f00c174 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/InterpreterController.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/interpreter/InterpreterController.java @@ -451,25 +451,25 @@ private void updateLineRepository(CobolLine line) throws IOException { } } - if (reader.isFlagSet(Constants.WORKING_STORAGE_SECTION) && - line.containsToken(Constants.EXEC_SQL_TOKEN) && - (line.containsToken(Constants.INCLUDE) - || reader.peekNextMeaningfulLine().containsToken(Constants.INCLUDE))) { - Platform platform = PlatformLookup.get(); - switch(platform){ - case ZOS: - if (line.containsToken("SQLCA") || line.containsToken("SQLDA")) - return; - default: - extractedCopyBook = lineRepository.addExpandedCopyDB2Statements(reader.readStatementAsOneLine()); - 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); - } - break; + 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)) { + Platform platform = PlatformLookup.get(); + switch(platform){ + case ZOS: + if (statement.contains("SQLCA") || statement.contains("SQLDA")) + return; + default: + extractedCopyBook = lineRepository.addExpandedCopyDB2Statements(reader.readStatementAsOneLine()); + 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); + } + break; + } } } } diff --git a/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/TestSuiteConcatenator.java b/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/TestSuiteConcatenator.java index 7ebd7c56..580ba18e 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/TestSuiteConcatenator.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/TestSuiteConcatenator.java @@ -7,6 +7,7 @@ import org.openmainframeproject.cobolcheck.services.Constants; import org.openmainframeproject.cobolcheck.services.Messages; import org.openmainframeproject.cobolcheck.services.StringHelper; +import org.openmainframeproject.cobolcheck.services.cobolLogic.replace.Replace; import org.openmainframeproject.cobolcheck.services.filehelpers.EncodingIO; import org.openmainframeproject.cobolcheck.services.filehelpers.FileNameMatcher; import org.openmainframeproject.cobolcheck.services.filehelpers.FilePermission; @@ -107,7 +108,7 @@ Reader concatenateTestSuites(String programTestSuiteSubdirectory) { String line = Constants.EMPTY_STRING; concatenatedTestSuitesWriter.write(StringHelper.commentOutLine("From file: " + matchingFile) + Constants.NEWLINE); while((line = testFileReader.readLine()) != null) { - concatenatedTestSuitesWriter.write(line + Constants.NEWLINE); + concatenatedTestSuitesWriter.write(Replace.replace(line) + Constants.NEWLINE); } testFileReader.close(); } diff --git a/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/VerifyMockCount.java b/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/VerifyMockCount.java index 48c1637c..fe7f75ed 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/VerifyMockCount.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/features/testSuiteParser/VerifyMockCount.java @@ -105,6 +105,7 @@ public List getArguments() { } public void addArgument(String argument) { + argument = argument.replaceAll("\\s+", " "); arguments.add(argument); } } 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 0f06ccf5..0b8a542c 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/NumericFields.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/NumericFields.java @@ -59,6 +59,7 @@ private void argumentCheck(Object argumentValue, String messageId) { private String getKeyBasedOnAssumedDataStructure(String line) { // We will attempt to split the line on any " IN " and " OF " statements, to isolate the names // in the referenced data structure. + // TODO - check the validity of the regex. It is not clear if it is correct. Because of the inclusion of the - sign. in W set. String[] nameTokens = line.toUpperCase().split("(?:^|\\W)OF(?:$|\\W)|(?:^|\\W)IN(?:$|\\W)"); Boolean found=false; for (String key : fieldTypes.keySet()) { 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 new file mode 100644 index 00000000..cc17b332 --- /dev/null +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/Replace.java @@ -0,0 +1,155 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.openmainframeproject.cobolcheck.services.log.Log; +import org.openmainframeproject.cobolcheck.services.log.LogLevel; + +import java.io.*; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class to handle the COBOL REPLACE statement on the test suite/test case source code. + *

+ * Method inspect() should be called on every line of the COBOL source code to set the REPLACE statements. + * The source must at least contain a char in the beginning of the line to be considered a valid source line. + * This char is the comment indicator. + * The REPLACE statement must be in the standard (IBM) format: REPLACE ==FROM-KEYWORD== BY ==TO-KEYWORD==. + *

+ * Method replace() is called on every line of the unit test source code to replace the strings. + *

+ * There are two main public methods: + *

    + *
  1. replace() - Used on Inspects the test source for replacing; replaceFrom to replaceTo strings.
  2. + *
  3. inspect() - Examines the source line for the REPLACE statement and sets the replaceFrom and replaceTo strings.
  4. + *
+ *

+ * And two convenience methods: + *

    + *
  1. isReplaceOn() - Returns the current state of the replaceOn flag.
  2. + *
  3. 2. reset() - Resets the state of the Replace class.
  4. + *
+ */ +public class Replace { + private static final String COBOL_COMMENT_INDICATOR = "*"; + + /** + * Look for the comment indicator in the source line. + * Capture group description: + * 1. 0-6 digits/spaces (group 1) the line numbers (if present) + * 2. * or space (group 2) comment indicator (other markers are not supported) + * 3. (group 3) remainder of the line + */ + private static final Pattern sourceIsCommentPattern = Pattern.compile("^([\\s|\\d]{0,6})(\\" + + COBOL_COMMENT_INDICATOR + ")(.+)"); + private static final int SOURCE_COMMENT_INDICATOR = 2; + + + /** + * The state of the REPLACE statement. + */ + private static boolean replaceOn = false; + private static final LinkedList replaceMap = new LinkedList<>(); + + private static boolean inspect_performed = false; + private static boolean inspect_performed_warned = false; + + /** + * Looks in the source line for the replace-key and replaces is with the replace-to-value. + * + * @param source a line of cobol-check unit test code + * @return the source line there the appropriate replacement has been made + */ + public static String replace(String source) { + if (!inspect_performed) { + if (!inspect_performed_warned) { + inspect_performed_warned = true; + Log.warn("Replace.replace() called before inspect"); + } + } + // if there are no REPLACE statements, return the source line as is + if (!replaceOn) return source; + + // avoid null pointer exception + if (source == null || source.isEmpty()) { + return source; + } + + // is the source line a comment? quit now... + if (sourceLineIsComment(source)) { + return source; + } + + String replacesString = source; + + for (ReplaceSet replaceSet : replaceMap) { + Log.trace("Replace.replace(): Key: <" + replaceSet.getFrom() + ">, Value: <" + replaceSet.getTo() + ">"); + replacesString = replaceSet.replaceInline(replacesString); + if ((Log.level() == LogLevel.TRACE) && (!replacesString.equals(source))) { + Log.trace("Replace.replace(): Key: <" + replaceSet.getFrom() + ">, result: " + replacesString); + } + } + return replacesString; + } + + + public static void inspectProgram(File cobolProgram) { + Log.trace("Replace.inspectProgram(): Inspecting the COBOL program file: " + cobolProgram); + reset(); + + // Use the statement locator to find the REPLACE statements in the COBOL program + ReplaceStatementLocator rsl = new ReplaceStatementLocator(cobolProgram); + replaceMap.addAll(rsl.getReplaceSets()); + + if (replaceMap.isEmpty()) { + replaceOn = false; + } else { + replaceOn = true; + } + inspect_performed = true; + } + + /** + * Examines the source line for the COBOL commment indicator + * + * @param source a line of cobol-check unit test code + * @return true if the source line is a comment, false otherwise + */ + private static boolean sourceLineIsComment(String source) { + Matcher sourceElements = sourceIsCommentPattern.matcher(source); + if (sourceElements.find()) { + // Is the line a comment? true or false + return sourceElements.group(SOURCE_COMMENT_INDICATOR).equals(COBOL_COMMENT_INDICATOR); + } + return false; + } + + /** + * Returns the current state of the replaceOn flag. + * + * @return true if there is an active REPLACE statement, false otherwise. + */ + public static boolean isReplaceOn() { + return replaceOn; + } + + /** + * Return the number of replace sets + * @return the number of replace sets + */ + public static int getReplaceSetsSize() { + return replaceMap.size(); + } + + /** + * Resets the state of the Replace class to the initial state. + */ + private static void reset() { + replaceOn = false; + replaceMap.clear(); + inspect_performed = false; + inspect_performed_warned = false; + } +} diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceSet.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceSet.java new file mode 100644 index 00000000..957c30ac --- /dev/null +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceSet.java @@ -0,0 +1,100 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ReplaceSet { + private String from; + private String to; + private boolean trailing; + private boolean leading; + + public ReplaceSet(String from, String to, boolean trailing, boolean leading) { + if (trailing && leading) { + throw new IllegalArgumentException("Cannot have both trailing and leading set to true"); + } + + this.from = from; + this.to = to; + this.trailing = trailing; + this.leading = leading; + } + + public ReplaceSet() { + this.from = ""; + this.to = ""; + this.trailing = false; + this.leading = false; + } + + /** + * Perform 'Replace' in the string (line param). Correponding to the 'REPLACE' statement in COBOL program + * And the values parsed from the statements are used to replace the values in the line. + * + * @param line The line to replace in + * @return The line with the replacement + */ + public String replaceInline(String line) { + if (!trailing && !leading) { + // We match with no case sensitivity by using the (?i) flag + return line.replaceAll("(?i)"+from, to); + } + else { + // Leading or trailing is set to true + String patternStr = ""; + if (trailing) { + patternStr = "(?i)\\S+(" + from + ")"; + } else { + patternStr = "(?i)(" + from + ")\\S+"; + } + + Pattern pattern = Pattern.compile(patternStr); + Matcher matcher = pattern.matcher(line); + //Iterate through the matches and replace the from with to + while (matcher.find()) { + String match = matcher.group(); + line = line.replace(match, match.replace(from, to)); + } + return line; + } + } + + public void setTrailing(boolean trailing) { + if (trailing && this.leading) { + throw new IllegalArgumentException("Cannot have both trailing and leading set to true"); + } + this.trailing = trailing; + } + + public void setLeading(boolean leading) { + if (leading && this.trailing) { + throw new IllegalArgumentException("Cannot have both trailing and leading set to true"); + } + this.leading = leading; + } + + public void setFrom(String value) { + this.from = value; + } + + public void setTo(String value) { + this.to = value; + } + + public String getFrom() { + return from; + } + + public String getTo() { + return to; + } + + public boolean isTrailing() { + return trailing; + } + + public boolean isLeading() { + return leading; + } +} diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceStatementLocator.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceStatementLocator.java new file mode 100644 index 00000000..06d274fc --- /dev/null +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceStatementLocator.java @@ -0,0 +1,122 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.openmainframeproject.cobolcheck.services.log.Log; + +import java.io.*; +import java.util.LinkedList; + +/** + * This class is used to find REPLACE statements in the source code. + */ +public class ReplaceStatementLocator { + + private LinkedList replaceSets = new LinkedList<>(); + + // while going through the file, we need to keep track of the current statement + // because it can be split over multiple lines + private final ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + protected StringBuilder currentStatement; + protected boolean we_are_parsing_a_replace_statement = false; + protected int sourceLinesProcessed = 0; + protected int commentLinesFound = 0; + + public ReplaceStatementLocator() { + Log.trace("ReplaceStatementLocator(): No file provided, only for testing purposes"); + } + + public ReplaceStatementLocator(File cobolFile) { + Log.trace("ReplaceStatementLocator(): Inspecting the COBOL program file: " + cobolFile); + //Iterate over the file and inspect each line + try (BufferedReader reader = new BufferedReader(new FileReader(cobolFile))) { + String line; + while ((line = reader.readLine()) != null) { + accumulateStatement(line); + } + } catch (FileNotFoundException e) { + Log.error("ReplaceStatementLocator(): File not found: " + e.getMessage()); + throw new RuntimeException(e); + } catch (IOException e) { + Log.error("ReplaceStatementLocator(): Error reading the COBOL program file: " + e.getMessage()); + throw new RuntimeException(e); + } + } + + public LinkedList getReplaceSets() { + return replaceSets; + } + + protected void accumulateStatement(String line) { + // tokenize the line + tokenizer.tokenize(line); + + if (tokenizer.isComment()) { + commentLinesFound++; + return; + } + + sourceLinesProcessed++; + ReplaceToken t; + // loop through the tokens + while (tokenizer.hasMoreTokens()) { + t = tokenizer.nextToken(); + if (we_are_parsing_a_replace_statement) { + // if we are parsing a REPLACE statement, accumulate the tokens + currentStatement.append(" ").append(t.getValue()); + } + if (t.getType() == ReplaceTokenType.REPLACE) { + // if we have a REPLACE token, start accumulating the statement + currentStatement = new StringBuilder().append(t.getValue()); + we_are_parsing_a_replace_statement = true; + } else if (t.getType() == ReplaceTokenType.TERMINATOR && we_are_parsing_a_replace_statement) { + // if we have a terminator token, process the statement + createStatements(currentStatement.toString()); + we_are_parsing_a_replace_statement = false; + } + } + } + + + /** + * process a complete REPLACE statement and create the ReplaceSet objects + * @param statement string of tokens from replace to terminator (.) + */ + protected void createStatements(String statement) { + ReplaceTokenizer statementTokenizer = new ReplaceTokenizer(); + statementTokenizer.tokenize(statement); + + ReplaceSet replaceSet = new ReplaceSet(); + + ReplaceToken t; + boolean nextTokenIsTo = false; + + while (statementTokenizer.hasMoreTokens()) { + t = statementTokenizer.nextToken(); + + // any replace and by keywords are ignored + // the tokens with other type, are used for the from/to keyset + switch (t.getType()) { + case REPLACE: + break; + case BY: + nextTokenIsTo = true; + break; + case LEADING: + replaceSet.setLeading(true); + break; + case TRAILING: + replaceSet.setTrailing(true); + break; + case OTHER: + if (nextTokenIsTo) { + replaceSet.setTo(t.getValue().replace("==", "")); + nextTokenIsTo = false; + replaceSets.add(replaceSet); + replaceSet = new ReplaceSet(); + } else { + replaceSet.setFrom(t.getValue().replace("==", "")); + } + break; + } + } + } +} diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceToken.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceToken.java new file mode 100644 index 00000000..7b7c5451 --- /dev/null +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceToken.java @@ -0,0 +1,43 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +public class ReplaceToken { + private String value; + private ReplaceTokenType type; + + public ReplaceToken(String value) { + this.value = value.trim(); + this.setType(); + } + + private void setType() { + switch (this.value.toUpperCase()) { + case "REPLACE": + this.type = ReplaceTokenType.REPLACE; + break; + case "LEADING": + this.type = ReplaceTokenType.LEADING; + break; + case "TRAILING": + this.type = ReplaceTokenType.TRAILING; + break; + case "BY": + this.type = ReplaceTokenType.BY; + break; + case ".": + this.type = ReplaceTokenType.TERMINATOR; + break; + default: + this.type = ReplaceTokenType.OTHER; + break; + + } + } + + public String getValue() { + return value; + } + + public ReplaceTokenType getType() { + return type; + } +} diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenType.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenType.java new file mode 100644 index 00000000..dd198a79 --- /dev/null +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenType.java @@ -0,0 +1,10 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +public enum ReplaceTokenType { + REPLACE, + LEADING, + TRAILING, + BY, + TERMINATOR, + OTHER +} diff --git a/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenizer.java b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenizer.java new file mode 100644 index 00000000..9fbc3f23 --- /dev/null +++ b/src/main/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenizer.java @@ -0,0 +1,82 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import java.util.LinkedList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Tokenizes a line and create tokens. All tokens are evaluated to a REPLACE statement + */ +public class ReplaceTokenizer { + private int position; + private final LinkedList tokens = new LinkedList<>(); + private boolean isComment; + private static final int GROUP_COMMENT_INDICATOR = 2; + private static final String COMMENT_INDICATOR = "*"; + + + /** + * Pattern to match all tokens in a line + *

+ * This pattern will match all non-space characters or a dot. + * The lookBack (? + * This pattern will match a line that starts with 6 spaces or digits, followed by a space or a star. + * The check is lenient, so it will also match lines that start with 0 to 5 spaces or digits. + */ + private static final Pattern isCommentPattern = Pattern.compile("^([\\s|\\d]{0,6})([\\*|\\s])(\\s*)", Pattern.CASE_INSENSITIVE); + + /** + * Tokenize a line. Every time a line is tokenized, the tokens are stored in a list, replacing the previous list. + * Use method hasMoreTokens() and nextToken() to get the tokens. + * @param line The line to tokenize + */ + public void tokenize(String line) { + this.position = 0; + tokens.clear(); + isComment = false; + Matcher isCommentMatcher = isCommentPattern.matcher(line); + if (isCommentMatcher.find()) { + // Is the line a comment? then stop processing + isComment = isCommentMatcher.group(GROUP_COMMENT_INDICATOR).equals(COMMENT_INDICATOR); + if (isComment) { + return; + } + } + Matcher allTokens = replacePattern.matcher(line); + + while (allTokens.find()) { + String token = allTokens.group(); + tokens.add(new ReplaceToken(token)); + } + } + + /** + * Check if there are more tokens + * @return true if there are more tokens + */ + public boolean hasMoreTokens() { + return (position < tokens.size()); + } + + /** + * Get the next token. Tokens are read in order and can only be read once. + * @return The next token. It is the type of ReplaceToken + */ + public ReplaceToken nextToken() { + return tokens.get(position++); + } + + /** + * Check if the line is a comment + * @return true if the line is a comment + */ + public boolean isComment() { + return isComment; + } +} diff --git a/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java b/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java index 282dd518..b55642f7 100644 --- a/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java +++ b/src/main/java/org/openmainframeproject/cobolcheck/workers/Generator.java @@ -11,13 +11,15 @@ import org.openmainframeproject.cobolcheck.exceptions.PossibleInternalLogicErrorException; import org.openmainframeproject.cobolcheck.features.interpreter.InterpreterController; import org.openmainframeproject.cobolcheck.features.prepareMerge.PrepareMergeController; -import org.openmainframeproject.cobolcheck.features.testSuiteParser.Mock; import org.openmainframeproject.cobolcheck.features.testSuiteParser.MockGenerator; import org.openmainframeproject.cobolcheck.features.testSuiteParser.TestSuiteParserController; import org.openmainframeproject.cobolcheck.features.writer.WriterController; +import org.openmainframeproject.cobolcheck.services.Config; import org.openmainframeproject.cobolcheck.services.Constants; import org.openmainframeproject.cobolcheck.services.Messages; import org.openmainframeproject.cobolcheck.services.RunInfo; +import org.openmainframeproject.cobolcheck.services.cobolLogic.replace.Replace; +import org.openmainframeproject.cobolcheck.services.filehelpers.PathHelper; import org.openmainframeproject.cobolcheck.services.log.Log; /** @@ -66,6 +68,8 @@ public Generator(InterpreterController interpreter, WriterController writerContr public void prepareAndRunMerge(String programName, String testFileNames) { RunInfo.setCurrentProgramName(new File(programName).getName()); RunInfo.setCurrentProgramPath(new File(programName).getAbsolutePath()); + Replace.inspectProgram(new File(PathHelper.appendMatchingFileSuffix(programName, Config.getApplicationFilenameSuffixes()))); + matchingTestDirectories = PrepareMergeController.getMatchingTestDirectoriesForProgram(programName); for (String matchingDirectory : matchingTestDirectories) { diff --git a/src/test/cobol/BIPM012/replac_more.cut b/src/test/cobol/BIPM012/replac_more.cut new file mode 100644 index 00000000..7d7864aa --- /dev/null +++ b/src/test/cobol/BIPM012/replac_more.cut @@ -0,0 +1,15 @@ +TESTSUITE 'TEST SUITE FOR MORE REPLACE ' + +TESTCASE 'TEST CASE #2 - COPYBOOK FIELD - ANOTHER FIELD' + + MOVE +100 TO USERNO IN BIPM012-PARM + PERFORM 100-MOVE-DATA + EXPECT USERNO IN BIPM012-PARM TO BE NUMERIC 100 + + +TESTCASE 'TEST CASE #3 - COPYBOOK FIELD - REVERSE NOTATION ' + + MOVE 5 TO MAX-HEIGHT IN BIPM012-PARM + PERFORM 100-MOVE-DATA + EXPECT MAX-HEIGHT IN BIPM012-PARM TO BE NUMERIC 5 + . \ No newline at end of file diff --git a/src/test/cobol/BIPM012/replacing.cut b/src/test/cobol/BIPM012/replacing.cut new file mode 100644 index 00000000..c6878f2e --- /dev/null +++ b/src/test/cobol/BIPM012/replacing.cut @@ -0,0 +1,31 @@ +TESTSUITE 'TEST SUITE FOR REPLACE ' + +TESTCASE 'TEST CASE #0 - WS FIELD - ALFANUM' + + MOVE '1970-01-01' TO WS-DATE-BEFORE + PERFORM 100-MOVE-DATA + EXPECT WS-DATE-AFTER TO BE '1970-01-01' + + +TESTCASE 'TEST CASE #0 - WS FIELD - NUM' + + MOVE 53 TO MOVE-IDX + PERFORM 100-MOVE-DATA + EXPECT MOVE-IDX TO BE NUMERIC 53 + + +TESTCASE 'TEST CASE #1.1 - COPYBOOK FIELD - POSITIVE VALUE ' + + MOVE 1 TO MAX-LENGTH IN :BDSIXXX:-PARM + PERFORM 100-MOVE-DATA + EXPECT MAX-LENGTH IN :BDSIXXX:-PARM + TO BE NUMERIC 1 + + +TESTCASE 'TEST CASE #1.2 - COPYBOOK FIELD - NEGATIV VALUE ' + + MOVE -5 TO MAX-LENGTH IN :BDSIXXX:-PARM + PERFORM 100-MOVE-DATA + EXPECT MAX-LENGTH IN :BDSIXXX:-PARM + TO BE NUMERIC -5 + . \ No newline at end of file diff --git a/src/test/cobol/MOCK/MockCallTest.cut b/src/test/cobol/MOCK/MockCallTest.cut index 78e9d41d..659a6663 100644 --- a/src/test/cobol/MOCK/MockCallTest.cut +++ b/src/test/cobol/MOCK/MockCallTest.cut @@ -29,4 +29,19 @@ BOOK-PARAM in COBOL-STRUCTURE, OUTPUT-VALUE HAPPENED ONCE + + TestCase "parameters with IN are recognized correctly still" + MOVE SPACES TO OUTPUT-VALUE IN COBOL-STRUCTURE + MOCK CALL 'MYCOBOL' USING ACTION-PARAM, + BOOK-PARAM IN COBOL-STRUCTURE, + OUTPUT-VALUE IN COBOL-STRUCTURE + MOVE "MOCKED" TO OUTPUT-VALUE IN COBOL-STRUCTURE + END-MOCK + PERFORM 611-CALL-WITH-STRUCTURE + EXPECT OUTPUT-VALUE IN COBOL-STRUCTURE TO BE "MOCKED" + VERIFY CALL 'MYCOBOL' USING ACTION-PARAM, + BOOK-PARAM IN COBOL-STRUCTURE, + OUTPUT-VALUE IN COBOL-STRUCTURE + HAPPENED ONCE . + diff --git a/src/test/java/org/openmainframeproject/cobolcheck/TestSuiteParserParsingTest.java b/src/test/java/org/openmainframeproject/cobolcheck/TestSuiteParserParsingTest.java index 0f05a0c8..a5d60da7 100644 --- a/src/test/java/org/openmainframeproject/cobolcheck/TestSuiteParserParsingTest.java +++ b/src/test/java/org/openmainframeproject/cobolcheck/TestSuiteParserParsingTest.java @@ -927,4 +927,5 @@ public void test_stop_method() throws IOException { assertTrue(testSuiteParser.containStopValue("string,")); assertFalse(testSuiteParser.containStopValue("string")); } + } diff --git a/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceSetTest.java b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceSetTest.java new file mode 100644 index 00000000..4accd7df --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceSetTest.java @@ -0,0 +1,83 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReplaceSetTest { + + @Test + public void testReplaceSet() { + + // Create a Replace set + ReplaceSet replaceSet = new ReplaceSet("REPLACE", "REPLACED", false, false); + assertEquals("REPLACE", replaceSet.getFrom()); + assertEquals("REPLACED", replaceSet.getTo()); + assertFalse(replaceSet.isTrailing()); + assertFalse(replaceSet.isLeading()); + + replaceSet = new ReplaceSet("BRUCE", "CAITLYN", true, false); + assertEquals("BRUCE", replaceSet.getFrom()); + assertEquals("CAITLYN", replaceSet.getTo()); + assertTrue(replaceSet.isTrailing()); + assertFalse(replaceSet.isLeading()); + + replaceSet = new ReplaceSet("JOHNNY", "JAY", false, true); + assertEquals("JOHNNY", replaceSet.getFrom()); + assertEquals("JAY", replaceSet.getTo()); + assertFalse(replaceSet.isTrailing()); + assertTrue(replaceSet.isLeading()); + + // Having both trailing and leading set to true is not allowed + // This should throw an exception + // Not possible via constructor + assertThrows(IllegalArgumentException.class, () -> new ReplaceSet("JOHNNY", "JAY", true, true)); + // not possible via setter + ReplaceSet replaceSetLeading = new ReplaceSet("REPLACE", "REPLACED", false, true); + assertThrows(IllegalArgumentException.class, () -> replaceSetLeading.setTrailing(true)); + ReplaceSet replaceSetTrailing = new ReplaceSet("REPLACE", "REPLACED", true, false); + assertThrows(IllegalArgumentException.class, () -> replaceSetTrailing.setLeading(true)); + } + + @Test + public void testReplaceInline() { + ReplaceSet replaceSet = new ReplaceSet("JOHNNY", "JAY", false, false); + String from = "Johnny is behind the iconic late-night desk."; + String expected = "JAY is behind the iconic late-night desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + } + + @Test + public void testReplaceInlineTrailing() { + ReplaceSet replaceSet = new ReplaceSet("night", "day", true, false); + String from = "Johnny is behind the iconic late-night desk."; + String expected = "Johnny is behind the iconic late-day desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + + from = "Johnny is working day and night behind the iconic late-night desk."; + expected = "Johnny is working day and night behind the iconic late-day desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + + from = "Johnny is working day and night behind the iconic desk."; + expected = "Johnny is working day and night behind the iconic desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + + + } + + @Test + public void testReplaceInlineLeading() { + ReplaceSet replaceSet = new ReplaceSet("late", "early", false, true); + String from = "Johnny is behind the iconic late-night desk."; + String expected = "Johnny is behind the iconic early-night desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + + from = "Johnny is working early and late behind the iconic late-night desk."; + expected = "Johnny is working early and late behind the iconic early-night desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + + from = "Johnny is working early and late behind the iconic desk."; + expected = "Johnny is working early and late behind the iconic desk."; + assertEquals(expected, replaceSet.replaceInline(from)); + } +} diff --git a/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceStatementLocatorTest.java b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceStatementLocatorTest.java new file mode 100644 index 00000000..0e79242a --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceStatementLocatorTest.java @@ -0,0 +1,125 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.util.LinkedList; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; + +public class ReplaceStatementLocatorTest { + + @Test + public void testReplaceStatementLocator() { + // we test alle the lines in the file + // are read and processed + + + // Create a mock of ReplaceStatementLocator + // TODO: FIX MOCK: ReplaceStatementLocator mockedLocator = Mockito.mock(ReplaceStatementLocator.class); + + // Define the behavior of the accumulateStatement method + // TODO: FIX MOCK: Mockito.doNothing().when(mockedLocator).accumulateStatement(anyString()); + + File cobolFile = new File("./testfiles/REPLACE.CBL"); + ReplaceStatementLocator locator = new ReplaceStatementLocator(cobolFile); + + // Verify that the accumulateStatement method was called + // TODO: FIX MOCK: Mockito.verify(mockedLocator, times(8)).accumulateStatement(anyString()); + + // TODO; remove the counters when the mock is working + assertEquals(11, locator.commentLinesFound); + assertEquals(56, locator.sourceLinesProcessed); + + } + + @Test + public void testAccumulateStatement() { + + ReplaceStatementLocator locator = new ReplaceStatementLocator(); + locator.accumulateStatement("123245 REPLACE ==REPLACE== BY ==REPLACED==."); + assertEquals(1, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + + + + } + + @Test + public void testAccumulateStatementWithMultipleLines() { + ReplaceStatementLocator locator = new ReplaceStatementLocator(); + + locator.accumulateStatement("123245 REPLACE ==REPLACE== BY ==REPLACED== "); + locator.accumulateStatement("123456 ==PETER== BY ==PHIL==. "); + assertEquals(2, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + assertEquals("PETER", locator.getReplaceSets().get(1).getFrom()); + assertEquals("PHIL", locator.getReplaceSets().get(1).getTo()); + + } + + @Test + public void testAccumulateStatementWithMultipleLinesAndComments() { + ReplaceStatementLocator locator = new ReplaceStatementLocator(); + + locator.accumulateStatement("123456* REPLACE ==REPLACE== BY ==REPLACED== "); + locator.accumulateStatement("123456 REPLACE ==REPLACE== BY ==REPLACED== "); + locator.accumulateStatement("123456* REPLACE ==BEEF== BY ==SALAD== "); + locator.accumulateStatement("123456 ==PETER== BY ==PHIL==. "); + assertEquals(2, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + assertEquals("PETER", locator.getReplaceSets().get(1).getFrom()); + assertEquals("PHIL", locator.getReplaceSets().get(1).getTo()); + + + locator = new ReplaceStatementLocator(); + locator.accumulateStatement("* REPLACE ==REPLACE== BY ==REPLACED== "); + locator.accumulateStatement(" REPLACE ==REPLACE== BY ==REPLACED== "); + locator.accumulateStatement(" * REPLACE ==BEEF== BY ==SALAD== "); + locator.accumulateStatement(" ==PETER== BY ==PHIL==. "); + assertEquals(2, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + assertEquals("PETER", locator.getReplaceSets().get(1).getFrom()); + assertEquals("PHIL", locator.getReplaceSets().get(1).getTo()); + } + + @Test + public void testCreateStatements() { + + //one set of keywords + ReplaceStatementLocator locator = new ReplaceStatementLocator(); + locator.createStatements("REPLACE ==REPLACE== BY ==REPLACED=="); + assertEquals(1, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + + //Two sets of keywords + locator = new ReplaceStatementLocator(); + locator.createStatements("REPLACE ==REPLACE== BY ==REPLACED== ==PETER== BY ==PHIL=="); + assertEquals(2, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + assertEquals("PETER", locator.getReplaceSets().get(1).getFrom()); + assertEquals("PHIL", locator.getReplaceSets().get(1).getTo()); + + //Two sets of keywords with one leading and one trailing + locator = new ReplaceStatementLocator(); + locator.createStatements("REPLACE TRAILING ==REPLACE== BY ==REPLACED== LEADING ==PETER== BY ==PHIL=="); + assertEquals(2, locator.getReplaceSets().size()); + assertEquals("REPLACE", locator.getReplaceSets().get(0).getFrom()); + assertEquals("REPLACED", locator.getReplaceSets().get(0).getTo()); + assertEquals("PETER", locator.getReplaceSets().get(1).getFrom()); + assertEquals("PHIL", locator.getReplaceSets().get(1).getTo()); + assertTrue(locator.getReplaceSets().get(0).isTrailing()); + assertTrue(locator.getReplaceSets().get(1).isLeading()); + } +} 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 new file mode 100644 index 00000000..0305e01e --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTest.java @@ -0,0 +1,66 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ReplaceTest { + + @Test + public void test_General_setup() { + // test we can inspect multiple times and still have the correct state + // and number of replace sets + + Replace.inspectProgram(new File("./testfiles/REPLACE.CBL")); + assertTrue(Replace.isReplaceOn()); + assertEquals(5, Replace.getReplaceSetsSize()); + + Replace.inspectProgram(new File("./testfiles/SUBPROG-AFTER.CBL")); + assertFalse(Replace.isReplaceOn()); + assertEquals(0, Replace.getReplaceSetsSize()); + + Replace.inspectProgram(new File("./testfiles/REPLACE.CBL")); + assertTrue(Replace.isReplaceOn()); + assertEquals(5, Replace.getReplaceSetsSize()); + assertEquals("In Genesys, the lead singer is Phil",Replace.replace("In Genesys, the lead singer is Peter")); + } + + @Test + public void test_replace() { + Replace.inspectProgram(new File("./testfiles/REPLACE.CBL")); + assertEquals("In Genesys, the lead singer is Phil",Replace.replace("In Genesys, the lead singer is Peter")); + assertEquals(" MOVE 'CAITLIN' TO WS-OMEGA",Replace.replace(" MOVE 'BRUCE' TO WS-OMEGA")); + assertEquals(" MOVE 'SOFT' TO WS-OMEGA",Replace.replace(" MOVE 'SOFT' TO WS-OMEGA")); + // When lines are comments, they should not be replaced + assertEquals(" * MOVE 'BRUCE' TO WS-OMEGA",Replace.replace(" * MOVE 'BRUCE' TO WS-OMEGA")); + assertEquals(" * MOVE 'SOFT' TO WS-OMEGA",Replace.replace(" * MOVE 'SOFT' TO WS-OMEGA")); + + } + + @Test + public void test_replace_leading() { + Replace.inspectProgram(new File("./testfiles/REPLACE.CBL")); + assertEquals(" * USING WS-NOT-USED WS-OMEGA", Replace.replace(" * USING WS-NOT-USED WS-OMEGA")); + assertEquals(" MOVE 'B' TO UT-EXPECTED",Replace.replace(" MOVE 'B' TO :WS:-EXPECTED")); + assertEquals(" MOVE 'Y' TO UT-OMEGA",Replace.replace(" MOVE 'Y' TO :WS:-OMEGA")); + assertEquals(" MOVE 'Y' TO WS-:WS:",Replace.replace(" MOVE 'Y' TO WS-:WS:")); + } + + @Test + public void test_replace_trailing() { + Replace.inspectProgram(new File("./testfiles/REPLACE.CBL")); + assertEquals(" * USING WS-ALPHA WS-OMEGA", Replace.replace(" * USING WS-ALPHA WS-OMEGA")); + assertEquals(" MOVE 'B' TO UT-EXPECTED",Replace.replace(" MOVE 'B' TO UT-EXPECTED")); + assertEquals(" MOVE 'Y' TO UT-GAMMA",Replace.replace(" MOVE 'Y' TO UT-ALPHA")); + assertEquals(" MOVE 'Y' TO WS-EXPECTED",Replace.replace(" MOVE 'Y' TO WS-ACTUAL")); + } + + + + + +} \ No newline at end of file diff --git a/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenTest.java b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenTest.java new file mode 100644 index 00000000..cf71a40b --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenTest.java @@ -0,0 +1,37 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.junit.jupiter.api.Test; + +public class ReplaceTokenTest { + @Test + public void it_identifies_the_replace_tokens() { + ReplaceToken token = new ReplaceToken("REPLACE"); + assert(token.getType() == ReplaceTokenType.REPLACE); + + token = new ReplaceToken("LEADING"); + assert(token.getType() == ReplaceTokenType.LEADING); + + token = new ReplaceToken("TRAILING"); + assert(token.getType() == ReplaceTokenType.TRAILING); + + token = new ReplaceToken("from-value"); + assert(token.getType() == ReplaceTokenType.OTHER); + + token = new ReplaceToken("BY"); + assert(token.getType() == ReplaceTokenType.BY); + + token = new ReplaceToken("TO-value"); + assert(token.getType() == ReplaceTokenType.OTHER); + + token = new ReplaceToken("."); + assert(token.getType() == ReplaceTokenType.TERMINATOR); + + token = new ReplaceToken("OTHER"); + assert(token.getType() == ReplaceTokenType.OTHER); + + token = new ReplaceToken("*"); + assert(token.getType() == ReplaceTokenType.OTHER); + + + } +} diff --git a/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenizerTest.java b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenizerTest.java new file mode 100644 index 00000000..1ea9f2d5 --- /dev/null +++ b/src/test/java/org/openmainframeproject/cobolcheck/services/cobolLogic/replace/ReplaceTokenizerTest.java @@ -0,0 +1,207 @@ +package org.openmainframeproject.cobolcheck.services.cobolLogic.replace; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class ReplaceTokenizerTest { + + @Test + public void it_accepts_an_empty_string_as_source() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + tokenizer.tokenize(""); + assertFalse(tokenizer.hasMoreTokens()); + } + + @Test + public void it_can_tokenize_more_strings_resetting_every_time() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + tokenizer.tokenize("first line."); + + assertTrue(tokenizer.hasMoreTokens()); + ReplaceToken rt = tokenizer.nextToken(); + assertEquals("first", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals("line", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals(".", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + tokenizer.tokenize("second line."); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals("second", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals("line", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals(".", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + assertFalse(tokenizer.hasMoreTokens()); + + } + + + + @Test + public void it_tokenizes_a_single_line_replace_statement() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + tokenizer.tokenize("REPLACE ==MODULE== BY ==NEW-NAME==."); + assertTrue(tokenizer.hasMoreTokens()); + ReplaceToken rt = tokenizer.nextToken(); + // Looking at the tokens and the ReplaceToken class, + // we can see that the first token is "REPLACE" and the type is REPLACE + assertSame(rt.getType(), ReplaceTokenType.REPLACE); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.BY); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + assertFalse(tokenizer.hasMoreTokens()); + } + + @Test + public void it_tokenizes_various_lines() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + tokenizer.tokenize("Tokenize a line. Period is only terminator is followed by a whitespace char."); + + assertTrue(tokenizer.hasMoreTokens()); + ReplaceToken rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals("line", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals(".", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals("Period", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + + tokenizer.tokenize("Period.is.terminator.if.followed.by.a.whitespace.char."); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals("Period.is.terminator.if.followed.by.a.whitespace.char", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertEquals(".", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + assertFalse(tokenizer.hasMoreTokens()); + } + + @Test + public void it_tokenizes_a_part_of_a_statement() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + tokenizer.tokenize("==something== BY ==NEW-NAME==."); + + assertTrue(tokenizer.hasMoreTokens()); + ReplaceToken rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.BY); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.OTHER); + + assertTrue(tokenizer.hasMoreTokens()); + rt = tokenizer.nextToken(); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + assertFalse(tokenizer.hasMoreTokens()); + } + + + @Test + public void it_tokenizes_a_single_period() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + tokenizer.tokenize("."); + + assertTrue(tokenizer.hasMoreTokens()); + ReplaceToken rt = tokenizer.nextToken(); + assertEquals(".", rt.getValue()); + assertSame(rt.getType(), ReplaceTokenType.TERMINATOR); + + assertFalse(tokenizer.hasMoreTokens()); + } + + @Test + public void it_finds_a_comment() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + + tokenizer.tokenize(" * This is a comment"); + assertTrue(tokenizer.isComment()); + + tokenizer.tokenize("* 01 WS-NAME PIC X(10)."); + assertTrue(tokenizer.isComment()); + + tokenizer.tokenize("123456* replace ==from== BY ==to==."); + assertTrue(tokenizer.isComment()); + + tokenizer.tokenize("12* 01 WS-NAME PIC X(10)."); + assertTrue(tokenizer.isComment()); + + tokenizer.tokenize("123456- is not recognized as comment."); + assertFalse(tokenizer.isComment()); + + tokenizer.tokenize("123456D is not recognized as comment."); + assertFalse(tokenizer.isComment()); + } + + @Test + public void it_finds_sourcecode_lines() { + ReplaceTokenizer tokenizer = new ReplaceTokenizer(); + + tokenizer.tokenize(" 01 WS-NAME PIC X(10)."); + assertFalse(tokenizer.isComment()); + + tokenizer.tokenize("147258 01 WS-NAME PIC X(10)."); + assertFalse(tokenizer.isComment()); + + } + + + +} diff --git a/testCOBOLtests.cmd b/testCOBOLtests.cmd new file mode 100644 index 00000000..f550d75a --- /dev/null +++ b/testCOBOLtests.cmd @@ -0,0 +1,16 @@ +@echo off +setlocal enabledelayedexpansion + +if "%1"=="" ( +echo Usage: runCOBOLtest.cmd [jar-version] like ./runCOBOLtest.cmd 0.2.16 +exit /b 1 +) + +for %%f in (src\main\cobol\*) do ( +java -jar .\bin\cobol-check-%1.jar -p %%~nf +rem print the file name and pause for user input +echo 'We ran: ' %%~nf ', Press any key to continue or Ctrl+C to exit' +pause +) + +endlocal diff --git a/testfiles/REPLACE.CBL b/testfiles/REPLACE.CBL new file mode 100644 index 00000000..f9d11fd4 --- /dev/null +++ b/testfiles/REPLACE.CBL @@ -0,0 +1,67 @@ + ********************************************************************** + * AUTHOR: T. N. KRAMER + * DATE: 14 FEB 2025 + * PURPOSE: DEMONSTRATE REPLACE STATEMENT + ********************************************************************** + IDENTIFICATION DIVISION. + PROGRAM-ID. REPLDEMO. + 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. + 77 WS-SUBPROGRAM-NAME PIC X(08). + 77 UT-COMPARE-DEFAULT PIC X VALUE 'N'. + 77 UT-NORMAL-COMPARE PIC X VALUE 'N'. + 77 UT-ACTUAL PIC X. + 77 UT-EXPECTED PIC X. + 77 UT-TEST-CASE-COUNT PIC 9(4) VALUE 0. + 77 UT-TEST-CASE-COUNT PIC 9(4) VALUE 0. + + REPLACE TRAILING ==ACTUAL== BY ==EXPECTED==. + + 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 + . + REPLACE TRAILING ==ALPHA== BY ==GAMMA==. + + 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 + . + REPLACE LEADING ==:WS:== BY ==UT==. + 3002-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:-EXPECTED + MOVE "Y" TO :WS:-OMEGA + . + REPLACE ==Bruce== BY ==CAITLIN== + ==PETER== BY ==Phil==. + 3002-DYNAMIC-CALL + MOVE 'PETER' TO WS-ALPHA + MOVE 'BRUCE' TO WS-OMEGA + 9999-END. + . \ No newline at end of file diff --git a/vs-code-extension/CHANGELOG.md b/vs-code-extension/CHANGELOG.md index ad03d33c..6a210e97 100644 --- a/vs-code-extension/CHANGELOG.md +++ b/vs-code-extension/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to the "cobol-unit-test" extension will be documented in this file. Versioning according to SemVer: https://semver.org/ + + +## [0.4.10] 26.02.2025 +- Now using COBOL Check version 0.2.16 + ## [0.4.9] 09.12.2024 - Now CORRECTLY using COBOL Check version 0.2.14 diff --git a/vs-code-extension/Cobol-check/bin/cobol-check-0.2.14.jar b/vs-code-extension/Cobol-check/bin/cobol-check-0.2.16.jar similarity index 85% rename from vs-code-extension/Cobol-check/bin/cobol-check-0.2.14.jar rename to vs-code-extension/Cobol-check/bin/cobol-check-0.2.16.jar index 9563b8cf..ffb8d0b7 100644 Binary files a/vs-code-extension/Cobol-check/bin/cobol-check-0.2.14.jar and b/vs-code-extension/Cobol-check/bin/cobol-check-0.2.16.jar differ diff --git a/vs-code-extension/client/src/extension.ts b/vs-code-extension/client/src/extension.ts index 690371d2..1a0480ec 100644 --- a/vs-code-extension/client/src/extension.ts +++ b/vs-code-extension/client/src/extension.ts @@ -20,7 +20,7 @@ import { getContentFromFilesystem, MarkdownTestData, TestCase, testData, TestFil let externalVsCodeInstallationDir = vscode.extensions.getExtension("openmainframeproject.cobol-check-extension").extensionPath; let configPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/config.properties'); let defaultConfigPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/default.properties'); -let cobolCheckJarPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/bin/cobol-check-0.2.14.jar'); +let cobolCheckJarPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/bin/cobol-check-0.2.16.jar'); let currentPlatform = getOS(); diff --git a/vs-code-extension/client/src/services/TestTree.ts b/vs-code-extension/client/src/services/TestTree.ts index 0054cc7b..586f2e87 100644 --- a/vs-code-extension/client/src/services/TestTree.ts +++ b/vs-code-extension/client/src/services/TestTree.ts @@ -10,7 +10,7 @@ import { handleCobolCheckOut } from '../Helpers/ExtensionHelper'; const textDecoder = new TextDecoder('utf-8'); let externalVsCodeInstallationDir = vscode.extensions.getExtension("openmainframeproject.cobol-check-extension").extensionPath; let configPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/config.properties'); -let cobolCheckJarPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/bin/cobol-check-0.2.14.jar'); +let cobolCheckJarPath = appendPath(externalVsCodeInstallationDir, 'Cobol-check/bin/cobol-check-0.2.16.jar'); diff --git a/vs-code-extension/package.json b/vs-code-extension/package.json index 87f8efe7..6c7355d0 100644 --- a/vs-code-extension/package.json +++ b/vs-code-extension/package.json @@ -8,7 +8,7 @@ "Snippets" ], "description": "Extension for running unit tests in Cobol", - "version": "0.4.9", + "version": "0.4.10", "icon": "images/cobol-check-logo-small.png", "repository": { "type": "git", @@ -40,7 +40,7 @@ } ], "description": "Extension for running unit tests in Cobol", - "version": "0.4.9", + "version": "0.4.10", "icon": "images/cobol-check-logo-small.png", "repository": { "type": "git",