diff --git a/src/main/java/de/tum/in/test/api/ast/asserting/UnwantedNodesAssert.java b/src/main/java/de/tum/in/test/api/ast/asserting/UnwantedNodesAssert.java index a1352937..a6a9681b 100644 --- a/src/main/java/de/tum/in/test/api/ast/asserting/UnwantedNodesAssert.java +++ b/src/main/java/de/tum/in/test/api/ast/asserting/UnwantedNodesAssert.java @@ -34,8 +34,18 @@ public class UnwantedNodesAssert extends AbstractAssert errorMessage = UnwantedNode.getMessageForUnwantedNodesForAllFilesBelow(actual, - type.getNodeNameNodeMap()); + type.getNodeNameNodeMap(), excludeMainMethod); errorMessage.ifPresent(unwantedNodeMessageForAllJavaFiles -> failWithMessage( localized("ast.method.has_no") + System.lineSeparator() + unwantedNodeMessageForAllJavaFiles)); //$NON-NLS-1$ return this; diff --git a/src/main/java/de/tum/in/test/api/ast/model/JavaFile.java b/src/main/java/de/tum/in/test/api/ast/model/JavaFile.java index 5ddf465e..a5b7c619 100644 --- a/src/main/java/de/tum/in/test/api/ast/model/JavaFile.java +++ b/src/main/java/de/tum/in/test/api/ast/model/JavaFile.java @@ -13,6 +13,8 @@ import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.MethodDeclaration; /** * Stores all required information about a Java file to be analyzed @@ -26,11 +28,22 @@ public class JavaFile { private final Path javaFilePath; private final CompilationUnit javaFileAST; - public JavaFile(Path javaFilePath, CompilationUnit javaFileAST) { + public JavaFile(Path javaFilePath, CompilationUnit javaFileAST, boolean excludeMainMethod) { this.javaFilePath = javaFilePath; + if (excludeMainMethod) { + excludeMainMethod(javaFileAST); + } this.javaFileAST = javaFileAST; } + private static void excludeMainMethod(CompilationUnit javaFileAST) { + javaFileAST.findAll(MethodDeclaration.class).stream() + .filter(method -> method.isStatic() && method.getNameAsString().equals("main") + && method.getParameters().size() == 1 && method.getType().isVoidType() + && method.getParameter(0).getTypeAsString().equals("String[]")) + .forEach(Node::remove); + } + public Path getJavaFilePath() { return javaFilePath; } @@ -47,12 +60,12 @@ public CompilationUnit getJavaFileAST() { * @return The information of the Java-file packed into a JavaFile object (null * if the file is not a Java-file) */ - public static JavaFile convertFromFile(Path pathOfFile) { + public static JavaFile convertFromFile(Path pathOfFile, Boolean excludeMainMethod) { if (!JAVAFILEMATCHER.matches(pathOfFile.getFileName())) { return null; } try { - return new JavaFile(pathOfFile, StaticJavaParser.parse(pathOfFile)); + return new JavaFile(pathOfFile, StaticJavaParser.parse(pathOfFile), excludeMainMethod); } catch (IOException e) { LOG.error("Error reading Java file '{}'", pathOfFile.toAbsolutePath(), e); //$NON-NLS-1$ throw new AssertionError(localized("ast.method.convert_from_file", pathOfFile.toAbsolutePath())); @@ -67,9 +80,9 @@ public static JavaFile convertFromFile(Path pathOfFile) { * list if the directory does not exist or if none of the files in the * directory or its subdirectories is a Java-file) */ - public static List readFromDirectory(Path pathOfDirectory) { + public static List readFromDirectory(Path pathOfDirectory, boolean excludeMainMethod) { try (Stream directoryContentStream = Files.walk(pathOfDirectory)) { - return directoryContentStream.map(JavaFile::convertFromFile).filter(Objects::nonNull) + return directoryContentStream.map(path -> convertFromFile(path, excludeMainMethod)).filter(Objects::nonNull) .collect(Collectors.toList()); } catch (IOException e) { LOG.error("Error reading Java files in '{}'", pathOfDirectory.toAbsolutePath(), e); //$NON-NLS-1$ diff --git a/src/main/java/de/tum/in/test/api/ast/model/UnwantedNode.java b/src/main/java/de/tum/in/test/api/ast/model/UnwantedNode.java index d1f6bc3f..2e13ec40 100644 --- a/src/main/java/de/tum/in/test/api/ast/model/UnwantedNode.java +++ b/src/main/java/de/tum/in/test/api/ast/model/UnwantedNode.java @@ -62,8 +62,8 @@ public static List getUnwantedNodesInJavaFile(JavaFile javaFile, * information (packed into UnwantedNode objects) */ public static Map> getUnwantedNodesForFileAt(Path pathOfJavaFile, - Map> nodesDefinedAsUnwanted) { - JavaFile javaFile = JavaFile.convertFromFile(pathOfJavaFile); + Map> nodesDefinedAsUnwanted, boolean excludeMainMethod) { + JavaFile javaFile = JavaFile.convertFromFile(pathOfJavaFile, excludeMainMethod); if (javaFile == null) { return Map.of(); } @@ -113,9 +113,9 @@ public static String getFormattedFileString(Path filePath, Map getMessageForUnwantedNodesForFileAt(Path pathOfJavaFile, - Map> nodeNameUnwantedNodeMap) { - Map> unwantedNodes = getUnwantedNodesForFileAt(pathOfJavaFile, - nodeNameUnwantedNodeMap); + Map> nodeNameUnwantedNodeMap, boolean excludeMainMethod) { + Map> unwantedNodes = getUnwantedNodesForFileAt(pathOfJavaFile, nodeNameUnwantedNodeMap, + excludeMainMethod); if (unwantedNodes.isEmpty()) { return Optional.empty(); } @@ -132,11 +132,11 @@ public static Optional getMessageForUnwantedNodesForFileAt(Path pathOfJa * @return Error message */ public static Optional getMessageForUnwantedNodesForAllFilesBelow(Path pathOfDirectory, - Map> nodeNameUnwantedNodeMap) { - return JavaFile.readFromDirectory(pathOfDirectory).stream() + Map> nodeNameUnwantedNodeMap, boolean excludeMainMethod) { + return JavaFile.readFromDirectory(pathOfDirectory, excludeMainMethod).stream() .sorted(Comparator.comparing(JavaFile::getJavaFilePath)) .map(javaFile -> getMessageForUnwantedNodesForFileAt(javaFile.getJavaFilePath(), - nodeNameUnwantedNodeMap)) + nodeNameUnwantedNodeMap, excludeMainMethod)) .filter(Optional::isPresent).map(Optional::get).map(message -> message + System.lineSeparator()) .reduce(String::concat).map(String::trim).map(message -> " " + message); } diff --git a/src/test/java/de/tum/in/test/integration/AstAssertionTest.java b/src/test/java/de/tum/in/test/integration/AstAssertionTest.java index d2545f17..4ef575a2 100644 --- a/src/test/java/de/tum/in/test/integration/AstAssertionTest.java +++ b/src/test/java/de/tum/in/test/integration/AstAssertionTest.java @@ -638,4 +638,28 @@ void test_testLevelIsNull() { "The 'level' is not set. Please use UnwantedNodesAssert.withLanguageLevel(LanguageLevel).")); } } + + @Nested + @DisplayName("Exclude-Main-Test-Tests") + class ExcludeMainTestTests { + + @TestTest + void test_testExcludeMain_Success() { + String testExcludeMain_Success = "testHasBelowNoLoopsOutsideMainMethod_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testExcludeMain_Success)); + } + + @TestTest + void test_testExcludeMain_Fail() { + String testExcludeMain_Fail = "testHasBelowNoLoopsOutsideMainMethod_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testExcludeMain_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "excludeMain", "yes", + "ClassWithLoopOutsideMainMethod.java") + + ":" + System.lineSeparator() + " - For-Statement was found:" + + System.lineSeparator() + " - Between line 6 (column 3) and line 8 (column 3)")); + } + } } diff --git a/src/test/java/de/tum/in/test/integration/testuser/AstAssertionUser.java b/src/test/java/de/tum/in/test/integration/testuser/AstAssertionUser.java index 599836a6..c7823687 100644 --- a/src/test/java/de/tum/in/test/integration/testuser/AstAssertionUser.java +++ b/src/test/java/de/tum/in/test/integration/testuser/AstAssertionUser.java @@ -436,4 +436,22 @@ void testLevelIsNull() { UnwantedNodesAssert.assertThatProjectSources().hasNo(LoopType.ANY); } } + + @Nested + @DisplayName("Exclude-Main-Tests") + class ExcludeErrorTests { + @Test + void testHasBelowNoLoopsOutsideMainMethod_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".excludeMain.no") + .excludeMainMethod().withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(LoopType.ANY); + } + + @Test + void testHasBelowNoLoopsOutsideMainMethod_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".excludeMain.yes") + .excludeMainMethod().withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(LoopType.ANY); + } + } } diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/excludeMain/no/ClassWithNoLoopsOutsideMainMethod.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/excludeMain/no/ClassWithNoLoopsOutsideMainMethod.java new file mode 100644 index 00000000..7176e34f --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/excludeMain/no/ClassWithNoLoopsOutsideMainMethod.java @@ -0,0 +1,57 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.excludeMain.no; + +public class ClassWithNoLoopsOutsideMainMethod { + + public void ifStatement() { + int x = 3; + if (x == 1) { + System.out.println("Hello"); + } else if (x == 0) { + System.out.println("World"); + } else { + System.out.println("!"); + } + } + + public void ifExpression() { + int x = 3; + System.out.println(x == 1 ? "Hello" : (x == 0 ? "World" : "!")); + } + + public void switchStatement() { + String output; + switch (3) { + case 1: + output = "Hello"; + break; + case 0: + output = "World"; + break; + default: + output = "!"; + break; + } + System.out.println(output); + } + + public void assertStatement() { + assert 3 == 0; + } + + public void throwStatement() throws Exception { + throw new Exception("This is a checked exception."); + } + + public void catchStatement() { + try { + throwStatement(); + } catch (Exception e) { + } + } + + public static void main(String[] args) { + for (int i = 0; i < args.length; i++) { + int x = i; + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/excludeMain/yes/ClassWithLoopOutsideMainMethod.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/excludeMain/yes/ClassWithLoopOutsideMainMethod.java new file mode 100644 index 00000000..fe6438c3 --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/excludeMain/yes/ClassWithLoopOutsideMainMethod.java @@ -0,0 +1,16 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.excludeMain.yes; + +public class ClassWithLoopOutsideMainMethod { + + public void forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public static void main(String[] args) { + if (args.length > 0) { + System.out.println("Hello World"); + } + } +}