Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

288 Enable COBOL Check to issue warnings/errors when a call statement is not mocked. #316

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ cobolcheck.test.program.path = ./
#---------------------------------------------------------------------------------------------------------------------
cobolcheck.test.program.name = CC##99.CBL

#---------------------------------------------------------------------------------------------------------------------
# if mock of call statements of a paragraph/section under test, are not present in a testcase
# then based on this value either error or warning will be raised.
# Note: When a call statement is inside a section/paragraph, where the section/paragraph is mocked,
# then the call statement is considered mocked.
# Default: false (warning will be raised)
#---------------------------------------------------------------------------------------------------------------------
cobolcheck.unmock.call.alerttype.error = false

#---------------------------------------------------------------------------------------------------------------------
# Path for the generated testsuite parse error log
# Default: ./
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.openmainframeproject.cobolcheck.exceptions;

public class UnMockedCallStatementException extends RuntimeException {
public UnMockedCallStatementException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@ public CobolLine getCurrentLineAsStatement() throws IOException {
return reader.readStatementAsOneLine();
}

public int getCurrentLineNumber() {
return reader.getLineNumber();
}

// Getting info from reader
public boolean hasStatementBeenRead() {
return reader.hasStatementBeenRead();
Expand Down Expand Up @@ -602,5 +606,34 @@ public void addSectionOrParagraphLines(List<String> lines){
}
}

public static class MockableComponent {
private String identifier;
private String type;
private List<String> arguments;
private int currentLineNumber;

public MockableComponent(String identifier, String type, List<String> arguments, int currentLineNumber) {
this.identifier = identifier;
this.type = type;
this.arguments = arguments;
this.currentLineNumber = currentLineNumber;
}

public String getIdentifier() {
return identifier;
}

public String getType() {
return type;
}

public List<String> getArguments() {
return arguments;
}

public int getCurrentLineNumber() {
return currentLineNumber;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,25 @@ public boolean mockExistsFor(String identifier, String type, List<String> argume
return false;
}

public boolean mockExistsFor(String identifier, String type, List<String> arguments, String testSuiteName, String testCaseName) {
for (Mock mock: mocks) {
if (mock.getIdentifier().equalsIgnoreCase(identifier) && mock.getType().equals(type)
&& mock.getArguments().equals(arguments)){
if (mock.getScope() == MockScope.Local){
if (mock.getTestSuiteName().equals(testSuiteName) && mock.getTestCaseName().equals(testCaseName)){
return true;
}
}
if (mock.getScope() == MockScope.Global){
if (mock.getTestSuiteName().equals(testSuiteName)){
return true;
}
}
}
}
return false;
}

public Mock getMockFor(String identifier, String type, String testSuite, String testCase, List<String> arguments){
List<Mock> globalMocks = new ArrayList<>();
for (Mock mock: mocks) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.openmainframeproject.cobolcheck.features.testSuiteParser;

import org.openmainframeproject.cobolcheck.exceptions.UnMockedCallStatementException;
import org.openmainframeproject.cobolcheck.services.Config;
import org.openmainframeproject.cobolcheck.services.Constants;
import org.openmainframeproject.cobolcheck.services.Messages;
Expand Down Expand Up @@ -148,6 +149,21 @@ public void logUnusedMocks(List<Mock> mocks){
}
}

public void logUnMockedCalls(String testSuiteName, String testCaseName, int currentCallLineNumber) {
String alertType = Config.getString(Constants.COBOLCHECK_UNMOCKCALL_ALERTTYPE_CONFIG_KEY, "false");
String testCaseNameToDisplay = (testCaseName.equals("")) ? "global testcase" : "testcase " + testCaseName;
if(alertType.equalsIgnoreCase("TRUE")) {
throw new UnMockedCallStatementException(
Messages.get("ERR033", String.valueOf(currentCallLineNumber), testCaseNameToDisplay, testSuiteName)
);
}
else {
Log.warn(
Messages.get("WRN009",String.valueOf(currentCallLineNumber), testCaseNameToDisplay, testSuiteName)
);
}
}

public void logVariableTypeMismatch(String expectedType, String actualType, String currentFile, int lineNumber, int lineIndex){
String error = "";
error += String.format(fileMessage, displayErrorType(ErrorTypes.WARNING), currentFile) + ":" + lineNumber + ":" + lineIndex + ":" + Constants.NEWLINE;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package org.openmainframeproject.cobolcheck.features.testSuiteParser;

import org.openmainframeproject.cobolcheck.exceptions.*;
import org.openmainframeproject.cobolcheck.features.interpreter.StringTokenizerExtractor;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going to ask for some rewriting here.
TestSuiteParser is a feature in the structure of COBOL Check.
As general rule, features should not be importing features.
I know there are exceptions to that, but it is not the case for TestSuiteParser.
You should be able to get the values you are looking for, from a services, instead of a feature.
Thank you

import org.openmainframeproject.cobolcheck.services.*;
import org.openmainframeproject.cobolcheck.services.cobolLogic.*;
import org.openmainframeproject.cobolcheck.services.log.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;

Expand All @@ -24,6 +27,7 @@ public class TestSuiteParser {
private final KeywordExtractor keywordExtractor;
private TestSuiteWritingStyle testSuiteWritingStyle;
private List<String> testSuiteTokens;
private HashMap<Map.Entry<String, String>, HashSet<String>> performParaOrSectionInTestCase;
private HashMap<String, HashSet<String>> testNamesHierarchy;
private String currentTestSuiteLine = "";
private int fileLineNumber = 0;
Expand Down Expand Up @@ -162,6 +166,7 @@ public TestSuiteParser(KeywordExtractor keywordExtractor, MockRepository mockRep
this.testSuiteErrorLog = testSuiteErrorLog;
testSuiteTokens = new ArrayList<>();
testNamesHierarchy = new HashMap<String, HashSet<String>>();
performParaOrSectionInTestCase = new HashMap<Map.Entry<String, String>, HashSet<String>>();
emptyTestSuite = true;
testCodePrefix = Config.getString(Constants.COBOLCHECK_PREFIX_CONFIG_KEY, Constants.DEFAULT_COBOLCHECK_PREFIX);
initializeCobolStatement();
Expand Down Expand Up @@ -662,6 +667,24 @@ public List<String> getParsedTestSuiteLines(BufferedReader testSuiteReader,
ignoreCobolStatementAndFieldNameKeyAction = false;
break;
}
if (cobolStatementInProgress) {
TokenExtractor tokenExtractor = new StringTokenizerExtractor();
List<String> currentCobolStatementTokens = tokenExtractor.extractTokensFrom(getCobolStatement());
Boolean isPerform = false;
for(String currentToken : currentCobolStatementTokens) {
if(currentToken.equalsIgnoreCase("PERFORM")) {
isPerform = true;
break;
}
}
if(isPerform) {
Map.Entry<String, String> currentTestSuiteTestCaseName = new AbstractMap.SimpleEntry<>(currentTestSuiteName, currentTestCaseName);
if(!performParaOrSectionInTestCase.containsKey(currentTestSuiteTestCaseName)) {
performParaOrSectionInTestCase.put(currentTestSuiteTestCaseName, new HashSet<String>());
}
performParaOrSectionInTestCase.get(currentTestSuiteTestCaseName).add(testSuiteToken);
}
}
if (CobolVerbs.isStartOrEndCobolVerb(testSuiteToken)) {
if ( cobolStatementInProgress) {
addUserWrittenCobolStatement(parsedTestSuiteLines);
Expand Down Expand Up @@ -1178,6 +1201,10 @@ public String getCurrentFieldName() {
return currentFieldName;
}

public HashMap<Map.Entry<String, String>, HashSet<String>> getPerformParaOrSectionFromTestCase() {
return performParaOrSectionInTestCase;
}

public WhenOther getWhenOtherSectionOrParagraph(String type, List<String> lines, String itdentifier, boolean withComments){
WhenOther whenOther = new WhenOther(testSuiteNumber, testCaseNumber, whenOtherNumber);
whenOther.addLines(lines);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.openmainframeproject.cobolcheck.exceptions.PossibleInternalLogicErrorException;
import org.openmainframeproject.cobolcheck.exceptions.TestSuiteCouldNotBeReadException;
import org.openmainframeproject.cobolcheck.features.interpreter.InterpreterController;
import org.openmainframeproject.cobolcheck.services.Config;
import org.openmainframeproject.cobolcheck.services.Constants;
import org.openmainframeproject.cobolcheck.services.Messages;
Expand All @@ -10,8 +11,11 @@

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public class TestSuiteParserController {

Expand Down Expand Up @@ -239,6 +243,10 @@ public boolean mockExistsFor(String identifier, String type, List<String> argume
return mockRepository.mockExistsFor(identifier, type, arguments);
}

public boolean mockExistsFor(String identifier, String type, List<String> arguments, String testSuiteName, String testCaseName) {
return mockRepository.mockExistsFor(identifier, type, arguments, testSuiteName, testCaseName);
}

/**Generates the lines for 'Evaluate when' to perform the correct generated SECTION
* for a specific identifier.
* @param identifier - The identifier of the SECTION, PARAGRAPH etc. that is mocked.
Expand Down Expand Up @@ -272,6 +280,26 @@ public void logUnusedMocks(){
testSuiteErrorLog.logUnusedMocks(mockRepository.getMocks());
}

public void logUnMockedCalls(HashMap<String, List<InterpreterController.MockableComponent>> mockableComponents) {
HashMap<Map.Entry<String, String>, HashSet<String>> performParaOrSectionInTestCase = testSuiteParser.getPerformParaOrSectionFromTestCase();
for (Map.Entry<Map.Entry<String, String>, HashSet<String>> testCaseEntry : performParaOrSectionInTestCase.entrySet()) {
String testSuiteName = testCaseEntry.getKey().getKey();
String testCaseName = testCaseEntry.getKey().getValue();
for (String paragraphOrSectionIdentifier : testCaseEntry.getValue()) {
if (mockExistsFor(paragraphOrSectionIdentifier, "PARAGRAPH", new ArrayList<>(), testSuiteName, testCaseName) ||
mockExistsFor(paragraphOrSectionIdentifier, "SECTION", new ArrayList<>(), testSuiteName, testCaseName))
continue;
if(mockableComponents.containsKey(paragraphOrSectionIdentifier)) {
for(InterpreterController.MockableComponent mockableComponent : mockableComponents.get(paragraphOrSectionIdentifier)){
if(!mockExistsFor(mockableComponent.getIdentifier(), mockableComponent.getType(), mockableComponent.getArguments(), testSuiteName, testCaseName)){
testSuiteErrorLog.logUnMockedCalls(testSuiteName, testCaseName, mockableComponent.getCurrentLineNumber());
}
}
}
}
}
}

/**
* Gets the lines from the cobol-check boilerplate copybooks, one for Working-Storage
* and one for Procedure Division.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ private Constants() {
public static final String DEFAULT_TEST_PROGRAM_NAME = "CC$$99.CBL";
public static final String PROCESS_CONFIG_KEY = ".process";
public static final String COBOLCHECK_PREFIX_CONFIG_KEY = "cobolcheck.prefix";
public static final String COBOLCHECK_UNMOCKCALL_ALERTTYPE_CONFIG_KEY = "cobolcheck.unmock.call.alerttype.error";
public static final String DEFAULT_COBOLCHECK_PREFIX = "UT-";
public static final String TEST_CODE_PREFIX_PLACEHOLDER = "==UT==";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.openmainframeproject.cobolcheck.exceptions.CobolSourceCouldNotBeReadException;
Expand Down Expand Up @@ -32,6 +34,8 @@ public class Generator {
private WriterController writerController;
private TestSuiteParserController testSuiteParserController;
private boolean workingStorageHasEnded;
private HashMap<String, List<InterpreterController.MockableComponent>> mockableComponentsHierarchy;
private String mockableComponentsHierarchyLastElementIdentifier = Constants.EMPTY_STRING;

List<String> matchingTestDirectories;

Expand All @@ -48,6 +52,7 @@ public Generator(InterpreterController interpreter, WriterController writerContr
this.interpreter = interpreter;
this.writerController = writerController;
this.testSuiteParserController = testSuiteParserController;
this.mockableComponentsHierarchy = new HashMap<>();
this.currentMockType=null;
mergeTestSuite();

Expand All @@ -65,6 +70,7 @@ 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());
this.mockableComponentsHierarchy = new HashMap<>();
matchingTestDirectories = PrepareMergeController.getMatchingTestDirectoriesForProgram(programName);
for (String matchingDirectory : matchingTestDirectories) {

Expand Down Expand Up @@ -103,6 +109,7 @@ private void mergeTestSuite() {
echoingSourceLineToOutput(sourceLine);
processingAfterEchoingSourceLineToOutput();
}
testSuiteParserController.logUnMockedCalls(mockableComponentsHierarchy);
testSuiteParserController.logUnusedMocks();
testSuiteParserController.prepareNextParse();
} catch (IOException ioEx) {
Expand Down Expand Up @@ -209,6 +216,14 @@ private void processingAfterEchoingSourceLineToOutput() throws IOException {
String identifier = interpreter.getPossibleMockIdentifier();
String type = interpreter.getPossibleMockType();
List<String> arguments = interpreter.getPossibleMockArgs();
InterpreterController.MockableComponent mockableComponent = new InterpreterController.MockableComponent(identifier, type, arguments, interpreter.getCurrentLineNumber());
if (type.equalsIgnoreCase(Constants.CALL_TOKEN) && !mockableComponentsHierarchy.isEmpty()) {
mockableComponentsHierarchy.get(mockableComponentsHierarchyLastElementIdentifier).add(mockableComponent);
}
if(type.equalsIgnoreCase(Constants.PARAGRAPH_TOKEN) || type.equalsIgnoreCase(Constants.SECTION_TOKEN)) {
mockableComponentsHierarchyLastElementIdentifier = identifier;
mockableComponentsHierarchy.put(identifier, new ArrayList<InterpreterController.MockableComponent>());
}
if (testSuiteParserController.mockExistsFor(identifier, type, arguments)){
if(interpreter.isInsideSectionOrParagraphMockBody()){
interpreter.addSectionOrParagraphLines(testSuiteParserController.generateMockPerformCalls(identifier, type, arguments));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ 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: Call Statement in Line %1$s of the source code is not mocked in %2$s of testSuite %3$s.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change to "not mocked in testcase "%2$s" " to indicate that the first value is the testcase.
And also put quotes around the testsuite name.
Thank you

Copy link
Contributor Author

@AkashKumar7902 AkashKumar7902 Sep 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rune-Christensen
Hey,
Sorry for not adding quotes.
I will be adding it in my new commit.

https://github.com/openmainframeproject/cobol-check/pull/316/files#diff-9d10e676c4992827a0cac77d70a1d1b995c80694ad957793df629043a5121121R154
I believe the above line is solving the "testcase" naming issue.
Thank You


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.
Expand All @@ -41,6 +42,7 @@ WRN005 = WRN005: Test results input file not found: %1$s. Define in config as te
WRN006 = WRN006: IOException writing test results to file: %1$s in ProcessOutputWriter.writeProcessOutputToFile(...)
WRN007 = WRN007: IOException reading test results from current process
WRN008 = WRN008: Access denied: Could not change permissions for %1$s
WRN009 = WRN009: Call Statement in Line %1$s of the source code is not mocked in %2$s of testSuite %3$s.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, thank you


INF001 = INF001: Attempting to load config from %1$s.
INF002 = INF002: Loaded config successfully from %1$s.
Expand Down
Loading
Loading