diff --git a/pom.xml b/pom.xml index d05dc48c..71e9e054 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,11 @@ java-string-similarity 2.0.0 + + com.github.javaparser + javaparser-core + 3.24.10 + org.junit.platform @@ -265,6 +270,7 @@ ${project.build.outputDirectory}/org/junit/ ${project.build.outputDirectory}/org/opentest4j/ ${project.build.outputDirectory}/sun/ + ${project.build.outputDirectory}/com/github/javaparser/ ${project.build.outputDirectory}/org/gradle/ diff --git a/src/main/java/de/tum/in/test/api/AresConfiguration.java b/src/main/java/de/tum/in/test/api/AresConfiguration.java new file mode 100644 index 00000000..dd57c48b --- /dev/null +++ b/src/main/java/de/tum/in/test/api/AresConfiguration.java @@ -0,0 +1,64 @@ +package de.tum.in.test.api; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import de.tum.in.test.api.util.ProjectSourcesFinder; + +/** + * Utility class for global Ares configuration. + * + * @author Christian Femers + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public class AresConfiguration { + + private AresConfiguration() { + } + + /** + * Returns the global Maven POM-file path used by Ares. + *

+ * Defaults to the relative path pom.xml. + * + * @return the configured pom.xml file path as string + */ + public static String getPomXmlPath() { + return ProjectSourcesFinder.getPomXmlPath(); + } + + /** + * Sets the global Maven POM-file path to the given file path string. + *

+ * Set by default to the relative path pom.xml. + * + * @param path the path as string, may be both relative or absolute + */ + public static void setPomXmlPath(String path) { + ProjectSourcesFinder.setPomXmlPath(path); + } + + /** + * Returns the global Gradle build file path used by Ares. + *

+ * Defaults to the relative path build.gradle. + * + * @return the configured gradle.build file path as string + */ + public static String getBuildGradlePath() { + return ProjectSourcesFinder.getBuildGradlePath(); + } + + /** + * Sets the global Gradle build file path to the given file path string. + *

+ * Set by default to the relative path build.gradle. + * + * @param path the path as string, may be both relative or absolute + */ + public static void setBuildGradlePath(String path) { + ProjectSourcesFinder.setBuildGradlePath(path); + } +} 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 new file mode 100644 index 00000000..a1352937 --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/asserting/UnwantedNodesAssert.java @@ -0,0 +1,128 @@ +package de.tum.in.test.api.ast.asserting; + +import static de.tum.in.test.api.localization.Messages.localized; +import static java.util.Objects.requireNonNull; +import static org.assertj.core.api.Assertions.fail; + +import java.nio.file.*; +import java.util.*; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.assertj.core.api.AbstractAssert; + +import com.github.javaparser.ParserConfiguration.LanguageLevel; +import com.github.javaparser.StaticJavaParser; + +import de.tum.in.test.api.AresConfiguration; +import de.tum.in.test.api.ast.model.UnwantedNode; +import de.tum.in.test.api.ast.type.*; +import de.tum.in.test.api.util.ProjectSourcesFinder; + +/** + * Checks whole Java files for unwanted nodes + * + * @author Markus Paulsen + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public class UnwantedNodesAssert extends AbstractAssert { + + /** + * The language level for the Java parser + */ + private final LanguageLevel level; + + private UnwantedNodesAssert(Path path, LanguageLevel level) { + super(requireNonNull(path), UnwantedNodesAssert.class); + this.level = level; + if (!Files.isDirectory(path)) { + fail("The source directory %s does not exist", path); //$NON-NLS-1$ + } + } + + /** + * Creates an unwanted node assertion object for all project source files. + *

+ * The project source directory gets extracted from the build configuration, and + * a pom.xml or build.gradle in the execution path is + * the default build configuration location. The configuration here is the same + * as the one in the structural tests and uses {@link AresConfiguration}. + * + * @return An unwanted node assertion object (for chaining) + */ + public static UnwantedNodesAssert assertThatProjectSources() { + var path = ProjectSourcesFinder.findProjectSourcesPath().orElseThrow(() -> new AssertionError("" //$NON-NLS-1$ + + "Could not find project sources folder." //$NON-NLS-1$ + + " Make sure the build file is configured correctly." //$NON-NLS-1$ + + " If it is not located in the execution folder directly," //$NON-NLS-1$ + + " set the location using AresConfiguration methods.")); //$NON-NLS-1$ + return new UnwantedNodesAssert(path, null); + } + + /** + * Creates an unwanted node assertion object for all source files at and below + * the given directory path. + * + * @param directory Path to a directory under which all files are considered + * @return An unwanted node assertion object (for chaining) + */ + public static UnwantedNodesAssert assertThatSourcesIn(Path directory) { + Objects.requireNonNull(directory, "The given source path must not be null."); //$NON-NLS-1$ + return new UnwantedNodesAssert(directory, null); + } + + /** + * Creates an unwanted node assertion object for all source files in the given + * package, including all of its sub-packages. + * + * @param packageName Java package name in the form of, e.g., + * de.tum.in.test.api, which is resolved + * relative to the path of this UnwantedNodesAssert. + * @return An unwanted node assertion object (for chaining) + * @implNote The package is split at "." with the resulting segments being + * interpreted as directory structure. So + * assertThatSourcesIn(Path.of("src/main/java")).withinPackage("net.example.test") + * will yield an assert for all source files located at and below the + * relative path src/main/java/net/example/test + */ + public UnwantedNodesAssert withinPackage(String packageName) { + Objects.requireNonNull(packageName, "The package name must not be null."); //$NON-NLS-1$ + var newPath = actual.resolve(Path.of("", packageName.split("\\."))); //$NON-NLS-1$ //$NON-NLS-2$ + return new UnwantedNodesAssert(newPath, level); + } + + /** + * Configures the language level used by the Java parser + * + * @param level The language level for the Java parser + * @return An unwanted node assertion object (for chaining) + */ + public UnwantedNodesAssert withLanguageLevel(LanguageLevel level) { + return new UnwantedNodesAssert(actual, level); + } + + /** + * Verifies that the selected Java files do not contain any syntax tree nodes + * (statements, expressions, ...) of the given type. + * + * @param type Unwanted statements + * @return This unwanted node assertion object (for chaining) + * @see ClassType + * @see ConditionalType + * @see ExceptionHandlingType + * @see LoopType + */ + public UnwantedNodesAssert hasNo(Type type) { + if (level == null) { + failWithMessage("The 'level' is not set. Please use UnwantedNodesAssert.withLanguageLevel(LanguageLevel)."); //$NON-NLS-1$ + } + StaticJavaParser.getParserConfiguration().setLanguageLevel(level); + Optional errorMessage = UnwantedNode.getMessageForUnwantedNodesForAllFilesBelow(actual, + type.getNodeNameNodeMap()); + 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 new file mode 100644 index 00000000..5ddf465e --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/model/JavaFile.java @@ -0,0 +1,79 @@ +package de.tum.in.test.api.ast.model; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.io.IOException; +import java.nio.file.*; +import java.util.*; +import java.util.stream.*; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.slf4j.*; + +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; + +/** + * Stores all required information about a Java file to be analyzed + */ +@API(status = Status.INTERNAL) +public class JavaFile { + + private static final PathMatcher JAVAFILEMATCHER = FileSystems.getDefault().getPathMatcher("glob:*.java"); //$NON-NLS-1$ + private static final Logger LOG = LoggerFactory.getLogger(JavaFile.class); + + private final Path javaFilePath; + private final CompilationUnit javaFileAST; + + public JavaFile(Path javaFilePath, CompilationUnit javaFileAST) { + this.javaFilePath = javaFilePath; + this.javaFileAST = javaFileAST; + } + + public Path getJavaFilePath() { + return javaFilePath; + } + + public CompilationUnit getJavaFileAST() { + return javaFileAST; + } + + /** + * Turns the Java-file into an AST in case the provided path points to a + * Java-file + * + * @param pathOfFile Path to the Java-file + * @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) { + if (!JAVAFILEMATCHER.matches(pathOfFile.getFileName())) { + return null; + } + try { + return new JavaFile(pathOfFile, StaticJavaParser.parse(pathOfFile)); + } 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())); + } + } + + /** + * Turns all Java-file below a certain path into ASTs + * + * @param pathOfDirectory Path to the highest analysis level + * @return List of Java-file information packed into JavaFile objects (empty + * 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) { + try (Stream directoryContentStream = Files.walk(pathOfDirectory)) { + return directoryContentStream.map(JavaFile::convertFromFile).filter(Objects::nonNull) + .collect(Collectors.toList()); + } catch (IOException e) { + LOG.error("Error reading Java files in '{}'", pathOfDirectory.toAbsolutePath(), e); //$NON-NLS-1$ + throw new AssertionError(localized("ast.method.read_from_directory", pathOfDirectory.toAbsolutePath())); + } + } +} diff --git a/src/main/java/de/tum/in/test/api/ast/model/NodePosition.java b/src/main/java/de/tum/in/test/api/ast/model/NodePosition.java new file mode 100644 index 00000000..bdb1d28f --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/model/NodePosition.java @@ -0,0 +1,61 @@ +package de.tum.in.test.api.ast.model; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.util.*; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.Position; +import com.github.javaparser.ast.Node; +import com.github.javaparser.utils.Pair; + +/** + * Stores information about the beginning and the end of a node + */ +@API(status = Status.INTERNAL) +public class NodePosition implements Comparable { + + private static final Comparator COMPARATOR = Comparator.comparing(NodePosition::getBeginLine) + .thenComparing(NodePosition::getBeginColumn); + + private final boolean hasBegin; + private final Pair begin; + private final boolean hasEnd; + private final Pair end; + + public NodePosition(Node node) { + Optional nodeBegin = node.getBegin(); + hasBegin = nodeBegin.isPresent(); + begin = hasBegin ? new Pair<>(nodeBegin.get().line, nodeBegin.get().column) : null; + Optional nodeEnd = node.getEnd(); + hasEnd = nodeEnd.isPresent(); + end = hasEnd ? new Pair<>(nodeEnd.get().line, nodeEnd.get().column) : null; + } + + public int getBeginLine() { + return begin.a; + } + + public int getBeginColumn() { + return begin.b; + } + + @Override + public String toString() { + return localized("ast.method.to_string", + (hasBegin ? localized("ast.check.has_begin_end", begin.a, begin.b) + : localized("ast.check.not_has_begin")), + (hasEnd ? localized("ast.check.has_begin_end", end.a, end.b) : localized("ast.check.not_has_end"))); + } + + public static NodePosition getPositionOf(Node node) { + return new NodePosition(node); + } + + @Override + public int compareTo(NodePosition o) { + return COMPARATOR.compare(this, o); + } +} 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 new file mode 100644 index 00000000..d1f6bc3f --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/model/UnwantedNode.java @@ -0,0 +1,143 @@ +package de.tum.in.test.api.ast.model; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.ast.Node; + +/** + * Stores all unwanted nodes of an abstract syntax tree of a Java-file + */ +@API(status = Status.INTERNAL) +public class UnwantedNode { + + private final String unwantedNodeName; + private final List unwantedNodePositions; + + public UnwantedNode(JavaFile javaFile, String unwantedNodeName, Class nodeDefinedAsUnwanted) { + this.unwantedNodeName = unwantedNodeName; + this.unwantedNodePositions = javaFile.getJavaFileAST().findAll(nodeDefinedAsUnwanted).stream() + .map(NodePosition::getPositionOf).collect(Collectors.toList()); + this.unwantedNodePositions.sort(NodePosition::compareTo); + } + + public String getUnwantedNodeName() { + return unwantedNodeName; + } + + public List getUnwantedNodePositions() { + return unwantedNodePositions; + } + + /** + * Finds all unwanted nodes in an abstract syntax tree of a Java-file + * + * @param javaFile Abstract syntax tree of a Java-file + * @param nodesDefinedAsUnwanted List of unwanted node information (packed into + * UnwantedNode objects) + */ + public static List getUnwantedNodesInJavaFile(JavaFile javaFile, + Map> nodesDefinedAsUnwanted) { + return nodesDefinedAsUnwanted.keySet().stream() + .map(unwantedNodeName -> new UnwantedNode(javaFile, unwantedNodeName, + nodesDefinedAsUnwanted.get(unwantedNodeName))) + .filter(unwantedNode -> !unwantedNode.getUnwantedNodePositions().isEmpty()) + .sorted(Comparator.comparing(uwn -> uwn.getUnwantedNodePositions().get(0))) + .collect(Collectors.toList()); + } + + /** + * Detects a provided list of unwanted nodes in a Java-File at a given path + * + * @param pathOfJavaFile Path to the Java-File, where unwanted nodes + * shall be detected + * @param nodesDefinedAsUnwanted List of unwanted nodes + * @return Map of File-Paths and their respective list of unwanted node + * information (packed into UnwantedNode objects) + */ + public static Map> getUnwantedNodesForFileAt(Path pathOfJavaFile, + Map> nodesDefinedAsUnwanted) { + JavaFile javaFile = JavaFile.convertFromFile(pathOfJavaFile); + if (javaFile == null) { + return Map.of(); + } + List unwantedNodes = getUnwantedNodesInJavaFile(javaFile, nodesDefinedAsUnwanted); + if (unwantedNodes.isEmpty()) { + return Map.of(); + } + return Map.of(pathOfJavaFile, unwantedNodes); + } + + /** + * Detects a provided list of unwanted nodes in Java-Files below a given path + * + * @param positionString Path to the Directory, at and below where unwanted + * nodes shall be detected + * @return List of pairs of File-Path and their respective information about + * unwanted nodes + */ + + public static String getFormattedPositionString(String positionString) { + return " - " + positionString; + } + + public static String getFormattedUnwantedNodeString(UnwantedNode unwantedNode) { + return unwantedNode + .getUnwantedNodePositions().stream().map(String::valueOf).map( + UnwantedNode::getFormattedPositionString) + .collect(Collectors.joining(System.lineSeparator(), + localized("ast.method.get_formatted_unwanted_node_string_prefix", + unwantedNode.getUnwantedNodeName()) + System.lineSeparator(), + "")); + } + + public static String getFormattedFileString(Path filePath, Map> unwantedNodes) { + return unwantedNodes.get(filePath).stream().map(UnwantedNode::getFormattedUnwantedNodeString) + .collect(Collectors.joining(System.lineSeparator(), + localized("ast.method.get_formatted_file_string_prefix", filePath) + System.lineSeparator(), + "")); + } + + /** + * Creates an error message in case unwanted files are detected + * + * @param pathOfJavaFile Path to the Java-File, where unwanted nodes + * shall be detected + * @param nodeNameUnwantedNodeMap List of unwanted nodes + * @return Error message + */ + public static Optional getMessageForUnwantedNodesForFileAt(Path pathOfJavaFile, + Map> nodeNameUnwantedNodeMap) { + Map> unwantedNodes = getUnwantedNodesForFileAt(pathOfJavaFile, + nodeNameUnwantedNodeMap); + if (unwantedNodes.isEmpty()) { + return Optional.empty(); + } + return Optional.of(unwantedNodes.keySet().stream() + .map(filePath -> getFormattedFileString(filePath, unwantedNodes)).reduce(String::concat).orElse("")); + } + + /** + * Creates an error message in case unwanted files are detected + * + * @param pathOfDirectory Path to the Directory, at and below where + * unwanted nodes shall be detected + * @param nodeNameUnwantedNodeMap List of unwanted nodes + * @return Error message + */ + public static Optional getMessageForUnwantedNodesForAllFilesBelow(Path pathOfDirectory, + Map> nodeNameUnwantedNodeMap) { + return JavaFile.readFromDirectory(pathOfDirectory).stream() + .sorted(Comparator.comparing(JavaFile::getJavaFilePath)) + .map(javaFile -> getMessageForUnwantedNodesForFileAt(javaFile.getJavaFilePath(), + nodeNameUnwantedNodeMap)) + .filter(Optional::isPresent).map(Optional::get).map(message -> message + System.lineSeparator()) + .reduce(String::concat).map(String::trim).map(message -> " " + message); + } +} diff --git a/src/main/java/de/tum/in/test/api/ast/type/ClassType.java b/src/main/java/de/tum/in/test/api/ast/type/ClassType.java new file mode 100644 index 00000000..b29388fe --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/type/ClassType.java @@ -0,0 +1,53 @@ +package de.tum.in.test.api.ast.type; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.util.Map; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.stmt.*; + +import de.tum.in.test.api.ast.asserting.UnwantedNodesAssert; + +/** + * Enumerates class Java statements which can be checked using + * {@link UnwantedNodesAssert}. + * + * @author Markus Paulsen + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public enum ClassType implements Type { + /** + * All class types + */ + ANY(Map.of(localized("ast.enum.class_type.class"), LocalClassDeclarationStmt.class, //$NON-NLS-1$ + localized("ast.enum.class_type.record"), LocalRecordDeclarationStmt.class)), + + /** + * The local class type (statements of the form: "class" + class name + "{" + + * "}") + */ + CLASS(Map.of(localized("ast.enum.class_type.class"), LocalClassDeclarationStmt.class)), //$NON-NLS-1$ + + /** + * The local record type (statements of the form: "record" + record name + "(" + + * record attributes + ")" + "{" + "}") + */ + RECORD(Map.of(localized("ast.enum.class_type.record"), LocalRecordDeclarationStmt.class)); //$NON-NLS-1$ + + private final Map> nodeNameNodeMap; + + ClassType(Map> nodeNameNodeMap) { + this.nodeNameNodeMap = nodeNameNodeMap; + } + + @Override + public Map> getNodeNameNodeMap() { + return nodeNameNodeMap; + } +} diff --git a/src/main/java/de/tum/in/test/api/ast/type/ConditionalType.java b/src/main/java/de/tum/in/test/api/ast/type/ConditionalType.java new file mode 100644 index 00000000..4828d9f6 --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/type/ConditionalType.java @@ -0,0 +1,74 @@ +package de.tum.in.test.api.ast.type; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.util.Map; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.expr.*; +import com.github.javaparser.ast.stmt.*; + +import de.tum.in.test.api.ast.asserting.UnwantedNodesAssert; + +/** + * Enumerates all conditional Java statements and expressions which can be + * checked using {@link UnwantedNodesAssert}. + * + * @author Markus Paulsen + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public enum ConditionalType implements Type { + /** + * All conditional types + */ + ANY(Map.of(localized("ast.enum.conditional_type.if"), IfStmt.class, //$NON-NLS-1$ + localized("ast.enum.conditional_type.conditional_expression"), ConditionalExpr.class, //$NON-NLS-1$ + localized("ast.enum.conditional_type.switch"), SwitchStmt.class, + localized("ast.enum.conditional_type.switch_expression"), SwitchExpr.class)), + /** + * All if-related types + */ + ANY_IF(Map.of(localized("ast.enum.conditional_type.if"), IfStmt.class, + localized("ast.enum.conditional_type.conditional_expression"), ConditionalExpr.class)), + /** + * The if statement type (statements of the form: "if (" + condition + ")" + + * statement) + */ + IFSTMT(Map.of(localized("ast.enum.conditional_type.if"), IfStmt.class)), //$NON-NLS-1$ + /** + * The conditional expression type (expression of the form: condition + "?" + + * expression + ":" + expression) + */ + CONDITIONALEXPR(Map.of(localized("ast.enum.conditional_type.conditional_expression"), ConditionalExpr.class)), + /** + * All switch-related types + */ + ANY_SWITCH(Map.of(localized("ast.enum.conditional_type.switch"), SwitchStmt.class, + localized("ast.enum.conditional_type.switch_expression"), SwitchExpr.class)), + /** + * The switch statement type (statements of the form: "switch (" + name + ")" + + * statement) + */ + SWITCHSTMT(Map.of(localized("ast.enum.conditional_type.switch"), SwitchStmt.class)), //$NON-NLS-1$ + /** + * The switch expression type (expression of the form: "switch (" + name + ")" + + * statement) + */ + SWITCHEXPR(Map.of(localized("ast.enum.conditional_type.switch_expression"), SwitchExpr.class)); + + private final Map> nodeNameNodeMap; + + ConditionalType(Map> nodeNameNodeMap) { + this.nodeNameNodeMap = nodeNameNodeMap; + } + + @Override + public Map> getNodeNameNodeMap() { + return nodeNameNodeMap; + } +} diff --git a/src/main/java/de/tum/in/test/api/ast/type/ExceptionHandlingType.java b/src/main/java/de/tum/in/test/api/ast/type/ExceptionHandlingType.java new file mode 100644 index 00000000..3569ba46 --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/type/ExceptionHandlingType.java @@ -0,0 +1,57 @@ +package de.tum.in.test.api.ast.type; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.util.Map; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.stmt.*; + +import de.tum.in.test.api.ast.asserting.UnwantedNodesAssert; + +/** + * Enumerates exception handling Java constructs which can be checked using + * {@link UnwantedNodesAssert}. + * + * @author Markus Paulsen + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public enum ExceptionHandlingType implements Type { + /** + * All exception handling types + */ + ANY(Map.of(localized("ast.enum.exception_handling_type.assert"), AssertStmt.class, //$NON-NLS-1$ + localized("ast.enum.exception_handling_type.throw"), ThrowStmt.class, //$NON-NLS-1$ + localized("ast.enum.exception_handling_type.catch"), //$NON-NLS-1$ + CatchClause.class)), + /** + * The assert type (statements of the form: "assert" + condition: boolean + + * errorMessage: String) + */ + ASSERT(Map.of(localized("ast.enum.exception_handling_type.assert"), AssertStmt.class)), //$NON-NLS-1$ + /** + * The throw type (statements of the form: "throw" + exception: Exception) + */ + THROW(Map.of(localized("ast.enum.exception_handling_type.throw"), ThrowStmt.class)), //$NON-NLS-1$ + /** + * The catch type (statements of the form: "catch (" + exception declaration + + * ")" + statement) + */ + CATCH(Map.of(localized("ast.enum.exception_handling_type.catch"), CatchClause.class)); //$NON-NLS-1$ + + private final Map> nodeNameNodeMap; + + ExceptionHandlingType(Map> nodeNameNodeMap) { + this.nodeNameNodeMap = nodeNameNodeMap; + } + + @Override + public Map> getNodeNameNodeMap() { + return nodeNameNodeMap; + } +} diff --git a/src/main/java/de/tum/in/test/api/ast/type/LoopType.java b/src/main/java/de/tum/in/test/api/ast/type/LoopType.java new file mode 100644 index 00000000..764f1e46 --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/type/LoopType.java @@ -0,0 +1,72 @@ +package de.tum.in.test.api.ast.type; + +import static de.tum.in.test.api.localization.Messages.localized; + +import java.util.Map; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.stmt.*; + +import de.tum.in.test.api.ast.asserting.UnwantedNodesAssert; + +/** + * Enumerates all Java loop types which can be checked using + * {@link UnwantedNodesAssert}. + * + * @author Markus Paulsen + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public enum LoopType implements Type { + /** + * All loop types + */ + ANY(Map.of(localized("ast.enum.loop_type.for"), ForStmt.class, localized("ast.enum.loop_type.for_each"), //$NON-NLS-1$ //$NON-NLS-2$ + ForEachStmt.class, localized("ast.enum.loop_type.while"), //$NON-NLS-1$ + WhileStmt.class, localized("ast.enum.loop_type.do_while"), DoStmt.class)), //$NON-NLS-1$ + /** + * All for-related types + */ + ANY_FOR(Map.of(localized("ast.enum.loop_type.for"), ForStmt.class, localized("ast.enum.loop_type.for_each"), //$NON-NLS-1$ //$NON-NLS-2$ + ForEachStmt.class)), + /** + * The for type (statements of the form: "for (" + declaration + condition + + * statement + ")" + statement) + */ + FORSTMT(Map.of(localized("ast.enum.loop_type.for"), ForStmt.class)), //$NON-NLS-1$ + /** + * The for each type (statements of the form: "for (" + declaration + ":" + + * iterateable: Iterateable ")" + statement) + */ + FOR_EACHSTMT(Map.of(localized("ast.enum.loop_type.for_each"), ForEachStmt.class)), //$NON-NLS-1$ + /** + * All while-related types + */ + ANY_WHILE(Map.of(localized("ast.enum.loop_type.while"), WhileStmt.class, localized("ast.enum.loop_type.do_while"), //$NON-NLS-1$ //$NON-NLS-2$ + DoStmt.class)), + /** + * The while type (statements of the form: "while (" + condition + ")" + + * statement) + */ + WHILESTMT(Map.of(localized("ast.enum.loop_type.while"), WhileStmt.class)), //$NON-NLS-1$ + /** + * The do while type (statements of the form: "do" + statement + "while (" + + * condition + ")") + */ + DO_WHILESTMT(Map.of(localized("ast.enum.loop_type.do_while"), DoStmt.class)); //$NON-NLS-1$ + + private final Map> nodeNameNodeMap; + + LoopType(Map> nodeNameNodeMap) { + this.nodeNameNodeMap = nodeNameNodeMap; + } + + @Override + public Map> getNodeNameNodeMap() { + return nodeNameNodeMap; + } +} diff --git a/src/main/java/de/tum/in/test/api/ast/type/Type.java b/src/main/java/de/tum/in/test/api/ast/type/Type.java new file mode 100644 index 00000000..b78f8ebf --- /dev/null +++ b/src/main/java/de/tum/in/test/api/ast/type/Type.java @@ -0,0 +1,30 @@ +package de.tum.in.test.api.ast.type; + +import java.util.Map; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; + +import com.github.javaparser.ast.Node; + +import de.tum.in.test.api.ast.asserting.UnwantedNodesAssert; + +/** + * Describes a type of syntactic construct in Java in the bread sense (e.g., all + * loop statements together can also be regarded as "the loop type"). Can be + * checked using {@link UnwantedNodesAssert}. + * + * @author Markus Paulsen + * @since 1.12.0 + * @version 1.0.0 + */ +@API(status = Status.MAINTAINED) +public interface Type { + + /** + * Returns the list of all node-name/node-type pairs + * + * @return Map of all node-name/node-type pairs + */ + Map> getNodeNameNodeMap(); +} diff --git a/src/main/java/de/tum/in/test/api/security/SecurityConstants.java b/src/main/java/de/tum/in/test/api/security/SecurityConstants.java index 7d017ba0..5693ab0a 100644 --- a/src/main/java/de/tum/in/test/api/security/SecurityConstants.java +++ b/src/main/java/de/tum/in/test/api/security/SecurityConstants.java @@ -40,7 +40,8 @@ public final class SecurityConstants { private static final Set STATIC_STACK_WHITELIST = Set.of("java.", "org.junit.", "jdk.", "org.eclipse.", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ "com.intellij", "org.assertj", "org.opentest4j.", "com.sun.", "sun.", "org.apache.", "de.tum.in.test.api", //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ - "net.jqwik.", "ch.qos.logback", "org.jacoco", "javax.", "org.json", "org.gradle", "worker.org.gradle"); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ + "net.jqwik.", "ch.qos.logback", "org.jacoco", "javax.", "org.json", "org.gradle", "worker.org.gradle", //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$ + "com.github.javaparser"); static final Set STACK_WHITELIST = Stream .concat(STATIC_STACK_WHITELIST.stream(), USER_DEFINED_STACK_WHITELIST.stream()) .collect(Collectors.toUnmodifiableSet()); diff --git a/src/main/java/de/tum/in/test/api/structural/testutils/ClassNameScanner.java b/src/main/java/de/tum/in/test/api/structural/testutils/ClassNameScanner.java index e8d0cb0e..f429a2a3 100644 --- a/src/main/java/de/tum/in/test/api/structural/testutils/ClassNameScanner.java +++ b/src/main/java/de/tum/in/test/api/structural/testutils/ClassNameScanner.java @@ -3,24 +3,20 @@ import static de.tum.in.test.api.localization.Messages.localized; import static de.tum.in.test.api.structural.testutils.ScanResultType.*; -import java.io.*; -import java.nio.file.*; +import java.io.File; +import java.nio.file.Path; import java.util.*; -import java.util.regex.Pattern; import java.util.stream.*; -import javax.xml.XMLConstants; -import javax.xml.parsers.*; - import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import org.slf4j.*; -import org.w3c.dom.*; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; import info.debatty.java.stringsimilarity.*; +import de.tum.in.test.api.AresConfiguration; +import de.tum.in.test.api.util.ProjectSourcesFinder; + /** * This class scans the submission project if the current expected class is * actually present in it or not. The result is returned as an instance of @@ -83,16 +79,6 @@ public class ClassNameScanner { private final Map> observedClasses = new HashMap<>(); private final ScanResult scanResult; - private static String pomXmlPath = "pom.xml"; //$NON-NLS-1$ - private static String buildGradlePath = "build.gradle"; //$NON-NLS-1$ - - /** - * Pattern for matching the assignment folder name for the build.gradle file of - * a Gradle project - */ - private static final Pattern gradleSourceDirPattern = Pattern - .compile("def\\s+assignmentSrcDir\\s*=\\s*\"(?

.+)\""); //$NON-NLS-1$ - public ClassNameScanner(String expectedClassName, String expectedPackageName) { this.expectedClassName = expectedClassName; this.expectedPackageName = expectedPackageName; @@ -220,89 +206,12 @@ private String createScanResultMessage(ScanResultType scanResultType, String fou * defined in the project build file (pom.xml or build.gradle) of the project. */ private void findObservedClassesInProject() { - String assignmentFolderName; - if (isMavenProject()) { - assignmentFolderName = getAssignmentFolderNameForMavenProject(); - } else if (isGradleProject()) { - assignmentFolderName = getAssignmentFolderNameForGradleProject(); + var assignmentFolderName = ProjectSourcesFinder.findProjectSourcesPath(); + if (assignmentFolderName.isPresent()) { + walkProjectFileStructure(assignmentFolderName.get(), assignmentFolderName.get().toFile(), observedClasses); } else { - LOG.error("Could not find any build file. Contact your instructor."); //$NON-NLS-1$ - return; - } - - if (assignmentFolderName == null) { - LOG.error("Could not retrieve source directory from project file. Contact your instructor."); //$NON-NLS-1$ - return; - } - walkProjectFileStructure(assignmentFolderName, new File(assignmentFolderName), observedClasses); - } - - private static boolean isMavenProject() { - if (pomXmlPath == null) - return false; - File projectFile = new File(pomXmlPath); - return projectFile.exists() && !projectFile.isDirectory(); - } - - private static boolean isGradleProject() { - if (buildGradlePath == null) - return false; - File projectFile = new File(buildGradlePath); - return projectFile.exists() && !projectFile.isDirectory(); - } - - /** - * Retrieves the assignment folder name for a maven project from the pom.xml - * - * @return the folder name of the maven project, relative to project root - */ - private static String getAssignmentFolderNameForMavenProject() { - try { - var pomFile = new File(pomXmlPath); - var documentBuilderFactory = DocumentBuilderFactory.newInstance(); - // make sure to avoid loading external files which would not be compliant - documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); //$NON-NLS-1$ - documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); //$NON-NLS-1$ - var documentBuilder = documentBuilderFactory.newDocumentBuilder(); - var pomXmlDocument = documentBuilder.parse(pomFile); - - NodeList buildNodes = pomXmlDocument.getElementsByTagName("build"); //$NON-NLS-1$ - for (var i = 0; i < buildNodes.getLength(); i++) { - var buildNode = buildNodes.item(i); - if (buildNode.getNodeType() == Node.ELEMENT_NODE) { - var buildNodeElement = (Element) buildNode; - var sourceDirectoryPropertyValue = buildNodeElement.getElementsByTagName("sourceDirectory").item(0) //$NON-NLS-1$ - .getTextContent(); - return sourceDirectoryPropertyValue.substring(sourceDirectoryPropertyValue.indexOf("}") + 2); //$NON-NLS-1$ - } - } - } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) { - LOG.error("Could not retrieve the source directory from the pom.xml file. Contact your instructor.", e); //$NON-NLS-1$ - } - return null; - } - - /** - * Retrieves the assignment folder name for a gradle project from the - * build.gradle - * - * @return the folder name of the gradle project, relative to project root - */ - private static String getAssignmentFolderNameForGradleProject() { - try { - var path = Path.of(buildGradlePath); - String fileContent = Files.readString(path); - - var matcher = gradleSourceDirPattern.matcher(fileContent); - if (matcher.find()) { - return matcher.group("dir"); //$NON-NLS-1$ - } - return null; - } catch (IOException | NullPointerException e) { - LOG.error("Could not retrieve the source directory from the build.gradle file. Contact your instructor.", //$NON-NLS-1$ - e); + LOG.error("Could not retrieve source directory from project file. Contact your instructor."); //$NON-NLS-1$ ´ } - return null; } /** @@ -310,14 +219,13 @@ private static String getAssignmentFolderNameForGradleProject() { * the assignment folder and adds each type it finds e.g. filenames ending with * .java and .kt to the passed JSON object. * - * @param assignmentFolderName The root folder where the method starts walking - * the project structure. - * @param node The current node the method is visiting. - * @param foundClasses The JSON object where the type names and packages - * get appended. + * @param assignmentFolder The root folder where the method starts walking the + * project structure. + * @param node The current node the method is visiting. + * @param foundClasses The JSON object where the type names and packages get + * appended. */ - private void walkProjectFileStructure(String assignmentFolderName, File node, - Map> foundClasses) { + private void walkProjectFileStructure(Path assignmentFolder, File node, Map> foundClasses) { // Example: // * assignmentFolderName: assignment/src // * fileName: assignment/src/de/tum/in/ase/eist/BubbleSort.java @@ -328,7 +236,7 @@ private void walkProjectFileStructure(String assignmentFolderName, File node, var fileNameComponents = fileName.split("\\."); //$NON-NLS-1$ var className = fileNameComponents[fileNameComponents.length - 2]; - Path packagePath = Path.of(assignmentFolderName).relativize(Path.of(node.getPath()).getParent()); + Path packagePath = assignmentFolder.relativize(Path.of(node.getPath()).getParent()); var packageName = StreamSupport.stream(packagePath.spliterator(), false).map(Object::toString) .collect(Collectors.joining(".")); //$NON-NLS-1$ @@ -342,24 +250,64 @@ private void walkProjectFileStructure(String assignmentFolderName, File node, String[] subNodes = node.list(); if (subNodes != null && subNodes.length > 0) for (String currentSubNode : subNodes) - walkProjectFileStructure(assignmentFolderName, new File(node, currentSubNode), foundClasses); + walkProjectFileStructure(assignmentFolder, new File(node, currentSubNode), foundClasses); } } + /** + * Returns the global Maven POM-file path used by Ares. + *

+ * Defaults to the relative path pom.xml. + * + * @return the configured pom.xml file path as string + * @deprecated Moved to a more general package. Please use + * {@link AresConfiguration#getPomXmlPath()} instead. + */ + @Deprecated(since = "1.12.0") public static String getPomXmlPath() { - return pomXmlPath; + return AresConfiguration.getPomXmlPath(); } + /** + * Sets the global Maven POM-file path to the given file path string. + *

+ * Set by default to the relative path pom.xml. + * + * @param path the path as string, may be both relative or absolute + * @deprecated Moved to a more general package. Please use + * {@link AresConfiguration#setPomXmlPath(String)} instead. + */ + @Deprecated(since = "1.12.0") public static void setPomXmlPath(String path) { - pomXmlPath = path; + AresConfiguration.setPomXmlPath(path); } + /** + * Returns the global Gradle build file path used by Ares. + *

+ * Defaults to the relative path build.gradle. + * + * @return the configured gradle.build file path as string + * @deprecated Moved to a more general package. Please use + * {@link AresConfiguration#getBuildGradlePath()} instead. + */ + @Deprecated(since = "1.12.0") public static String getBuildGradlePath() { - return buildGradlePath; + return AresConfiguration.getBuildGradlePath(); } + /** + * Sets the global Gradle build file path to the given file path string. + *

+ * Set by default to the relative path build.gradle. + * + * @param path the path as string, may be both relative or absolute + * @deprecated Moved to a more general package. Please use + * {@link AresConfiguration#setBuildGradlePath(String)} instead. + */ + @Deprecated(since = "1.12.0") public static void setBuildGradlePath(String path) { - buildGradlePath = path; + AresConfiguration.setBuildGradlePath(path); } static boolean isMisspelledWithHighProbability(String a, String b) { diff --git a/src/main/java/de/tum/in/test/api/util/ProjectSourcesFinder.java b/src/main/java/de/tum/in/test/api/util/ProjectSourcesFinder.java new file mode 100644 index 00000000..2026460d --- /dev/null +++ b/src/main/java/de/tum/in/test/api/util/ProjectSourcesFinder.java @@ -0,0 +1,141 @@ +package de.tum.in.test.api.util; + +import java.io.*; +import java.nio.file.*; +import java.util.Optional; +import java.util.regex.Pattern; + +import javax.xml.XMLConstants; +import javax.xml.parsers.*; + +import org.apiguardian.api.API; +import org.apiguardian.api.API.Status; +import org.slf4j.*; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +import de.tum.in.test.api.AresConfiguration; + +@API(status = Status.INTERNAL) +public class ProjectSourcesFinder { + + private static final Logger LOG = LoggerFactory.getLogger(ProjectSourcesFinder.class); + + /** + * Pattern for matching the assignment folder name for the build.gradle file of + * a Gradle project + */ + private static final Pattern GRADLE_SOURCE_DIR_PATTERN = Pattern + .compile("def\\s+assignmentSrcDir\\s*=\\s*\"(?

.+)\""); //$NON-NLS-1$ + + private static String pomXmlPath = "pom.xml"; //$NON-NLS-1$ + private static String buildGradlePath = "build.gradle"; //$NON-NLS-1$ + + /** + * Returns the project sources path depending on the Ares and project + * configuration. + *

+ * This methods extracts the source path from the project build file, depending + * on whether a pom.xml or build.gradle file was found this method looks. The + * location of the project build file can be configured using + * {@link AresConfiguration}. + * + * @return An empty optional if no project build file was found or if the build + * file did not contain the sources path in the format required by Ares. + */ + public static Optional findProjectSourcesPath() { + String assignmentFolderName = null; + if (isMavenProject()) { + assignmentFolderName = getAssignmentFolderNameForMavenProject(); + } else if (isGradleProject()) { + assignmentFolderName = getAssignmentFolderNameForGradleProject(); + } else { + LOG.error("Could not find any build file. Contact your instructor."); //$NON-NLS-1$ + } + return Optional.ofNullable(assignmentFolderName).map(Path::of); + } + + public static boolean isMavenProject() { + if (pomXmlPath == null) + return false; + File projectFile = new File(pomXmlPath); + return projectFile.exists() && !projectFile.isDirectory(); + } + + public static boolean isGradleProject() { + if (buildGradlePath == null) + return false; + File projectFile = new File(buildGradlePath); + return projectFile.exists() && !projectFile.isDirectory(); + } + + /** + * Retrieves the assignment folder name for a maven project from the pom.xml + * + * @return the folder name of the maven project, relative to project root + */ + private static String getAssignmentFolderNameForMavenProject() { + try { + var pomFile = new File(pomXmlPath); + var documentBuilderFactory = DocumentBuilderFactory.newInstance(); + // make sure to avoid loading external files which would not be compliant + documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, ""); //$NON-NLS-1$ + documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); //$NON-NLS-1$ + var documentBuilder = documentBuilderFactory.newDocumentBuilder(); + var pomXmlDocument = documentBuilder.parse(pomFile); + + NodeList buildNodes = pomXmlDocument.getElementsByTagName("build"); //$NON-NLS-1$ + for (var i = 0; i < buildNodes.getLength(); i++) { + var buildNode = buildNodes.item(i); + if (buildNode.getNodeType() == Node.ELEMENT_NODE) { + var buildNodeElement = (Element) buildNode; + var sourceDirectoryPropertyValue = buildNodeElement.getElementsByTagName("sourceDirectory").item(0) //$NON-NLS-1$ + .getTextContent(); + return sourceDirectoryPropertyValue.substring(sourceDirectoryPropertyValue.indexOf("}") + 2); //$NON-NLS-1$ + } + } + } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) { + LOG.error("Could not retrieve the source directory from the pom.xml file. Contact your instructor.", e); //$NON-NLS-1$ + } + return null; + } + + /** + * Retrieves the assignment folder name for a gradle project from the + * build.gradle + * + * @return the folder name of the gradle project, relative to project root + */ + private static String getAssignmentFolderNameForGradleProject() { + try { + var path = Path.of(buildGradlePath); + String fileContent = Files.readString(path); + + var matcher = GRADLE_SOURCE_DIR_PATTERN.matcher(fileContent); + if (matcher.find()) { + return matcher.group("dir"); //$NON-NLS-1$ + } + return null; + } catch (IOException | NullPointerException e) { + LOG.error("Could not retrieve the source directory from the build.gradle file. Contact your instructor.", //$NON-NLS-1$ + e); + } + return null; + } + + public static String getPomXmlPath() { + return pomXmlPath; + } + + public static void setPomXmlPath(String path) { + pomXmlPath = path; + } + + public static String getBuildGradlePath() { + return buildGradlePath; + } + + public static void setBuildGradlePath(String path) { + buildGradlePath = path; + } +} diff --git a/src/main/resources/de/tum/in/test/api/localization/messages.properties b/src/main/resources/de/tum/in/test/api/localization/messages.properties index 83c34286..1879c3f5 100644 --- a/src/main/resources/de/tum/in/test/api/localization/messages.properties +++ b/src/main/resources/de/tum/in/test/api/localization/messages.properties @@ -175,3 +175,28 @@ dynamics.method.not_found=Method %s %s not found. dynamics.method.null=Method %s could not be called, the object is null. dynamics.method.return=Method %s does not return %s. dynamics.method.static=Method %s is not static. +#AST +ast.method.has_no=Unwanted statement found: +ast.method.convert_from_file=The file %s could not be read. +ast.method.read_from_directory=The folder %s could not be read. +ast.method.to_string=Between %s and %s +ast.check.has_begin_end=line %s (column %s) +ast.check.not_has_begin=no begin available +ast.check.not_has_end=no end available +ast.method.get_formatted_unwanted_node_string_prefix=\u0020\u0020- %s was found: +ast.method.get_formatted_file_string_prefix=\u0020- In %s: + +ast.enum.loop_type.for=For-Statement +ast.enum.loop_type.for_each=For-Each-Statement +ast.enum.loop_type.while=While-Statement +ast.enum.loop_type.do_while=Do-While-Statement +ast.enum.conditional_type.if=If-Statement +ast.enum.conditional_type.conditional_expression=If-Expression +ast.enum.conditional_type.switch=Switch-Statement +ast.enum.conditional_type.switch_expression=Switch-Expression +ast.enum.exception_handling_type.assert=Assert-Statement +ast.enum.exception_handling_type.throw=Throw-Statement +ast.enum.exception_handling_type.catch=Catch-Statement +ast.enum.class_type.class=Local-Class-Statement +ast.enum.class_type.record=Local-Record-Statement + diff --git a/src/main/resources/de/tum/in/test/api/localization/messages_de.properties b/src/main/resources/de/tum/in/test/api/localization/messages_de.properties index da8aba48..c74209d1 100644 --- a/src/main/resources/de/tum/in/test/api/localization/messages_de.properties +++ b/src/main/resources/de/tum/in/test/api/localization/messages_de.properties @@ -145,3 +145,27 @@ dynamics.method.not_found=Keine Methode %s %s gefunden. dynamics.method.null=Methode %s konnte nicht aufgerufen werden, das Objekt ist null. dynamics.method.return=Methode %s gibt nicht %s zurück. dynamics.method.static=Methode %s ist nicht statisch. +#AST +ast.method.has_no=Unerwünschte Anweisung gefunden: +ast.method.convert_from_file=Die Datei %s konnte nicht gelesen werden. +ast.method.read_from_directory=Der Ordner %s konnte nicht gelesen werden. +ast.method.to_string=Zwischen %s und %s +ast.check.has_begin_end=Zeile %s (Spalte %s) +ast.check.not_has_begin=kein Anfang verügbar +ast.check.not_has_end=kein Ende verügbar +ast.method.get_formatted_unwanted_node_string_prefix= - %s wurde gefunden: +ast.method.get_formatted_file_string_prefix= - In %s: + +ast.enum.loop_type.for=For-Anweisung +ast.enum.loop_type.for_each=For-Each-Anweisung +ast.enum.loop_type.while=While-Anweisung +ast.enum.loop_type.do_while=Do-While-Anweisung +ast.enum.conditional_type.if=If-Anweisung +ast.enum.conditional_type.conditional_expression=If-Ausdruck +ast.enum.conditional_type.switch=Switch-Anweisung +ast.enum.conditional_type.switch_expression=Switch-Ausdruck +ast.enum.exception_handling_type.assert=Assert-Anweisung +ast.enum.exception_handling_type.throw=Throw-Anweisung +ast.enum.exception_handling_type.catch=Catch-Anweisung +ast.enum.class_type.class=Local-Class-Anweisung +ast.enum.class_type.record=Local-Record-Anweisung \ No newline at end of file diff --git a/src/test/java/de/tum/in/test/integration/AstAssertionTest.java b/src/test/java/de/tum/in/test/integration/AstAssertionTest.java new file mode 100644 index 00000000..d2545f17 --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/AstAssertionTest.java @@ -0,0 +1,641 @@ +package de.tum.in.test.integration; + +import static de.tum.in.test.testutilities.CustomConditions.*; + +import java.nio.file.Path; + +import org.junit.jupiter.api.*; +import org.junit.platform.testkit.engine.Events; + +import de.tum.in.test.integration.testuser.AstAssertionUser; +import de.tum.in.test.testutilities.*; + +@UserBased(AstAssertionUser.class) +public class AstAssertionTest { + + @UserTestResults + private static Events tests; + + public static void setTests(Events tests) { + AstAssertionTest.tests = tests; + } + + @Nested + @DisplayName("For-Loop-Test-Tests") + class ForLoopTestTests { + @TestTest + void test_testHasBelowNoForLoop_Success() { + String testHasBelowNoForLoop_Success = "testHasBelowNoForLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoForLoop_Success)); + } + + @TestTest + void test_testHasBelowNoForLoop_Fail() { + String testHasBelowNoForLoop_Fail = "testHasBelowNoForLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoForLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - For-Statement was found:" + + System.lineSeparator() + + " - Between line 8 (column 3) and line 10 (column 3)")); + } + } + + @Nested + @DisplayName("For-Each-Loop-Test-Tests") + class ForEachLoopTestTests { + @TestTest + void test_testHasBelowNoForEachLoop_Success() { + String testHasBelowNoForEachLoop_Success = "testHasBelowNoForEachLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoForEachLoop_Success)); + } + + @TestTest + void test_testHasBelowNoForEachLoop_Fail() { + String testHasBelowNoForEachLoop_Fail = "testHasBelowNoForEachLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoForEachLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - For-Each-Statement was found:" + + System.lineSeparator() + + " - Between line 14 (column 3) and line 16 (column 3)")); + } + } + + @Nested + @DisplayName("While-Loop-Test-Tests") + class WhileLoopTestTests { + + @TestTest + void test_testHasBelowNoWhileLoop_Success() { + String testHasBelowNoWhileLoop_Success = "testHasBelowNoWhileLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoWhileLoop_Success)); + } + + @TestTest + void test_testHasBelowNoWhileLoop_Fail() { + String testHasBelowNoWhileLoop_Fail = "testHasBelowNoWhileLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoWhileLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - While-Statement was found:" + + System.lineSeparator() + + " - Between line 21 (column 3) and line 24 (column 3)")); + } + } + + @Nested + @DisplayName("Do-While-Loop-Test-Tests") + class DoWhileLoopTestTests { + + @TestTest + void test_testHasBelowNoDoWhileLoop_Success() { + String testHasBelowNoDoWhileLoop_Success = "testHasBelowNoDoWhileLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoDoWhileLoop_Success)); + } + + @TestTest + void test_testHasBelowNoDoWhileLoop_Fail() { + String testHasBelowNoDoWhileLoop_Fail = "testHasBelowNoDoWhileLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoDoWhileLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - Do-While-Statement was found:" + + System.lineSeparator() + + " - Between line 29 (column 3) and line 32 (column 18)")); + } + } + + @Nested + @DisplayName("Any-For-Loop-Test-Tests") + class AnyForLoopTestTests { + + @TestTest + void test_testHasBelowNoAnyForLoop_Success() { + String testHasBelowNoAnyForLoop_Success = "testHasBelowNoAnyForLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyForLoop_Success)); + } + + @TestTest + void test_testHasBelowNoAnyForLoop_Fail() { + String testHasBelowNoAnyForLoop_Fail = "testHasBelowNoAnyForLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoAnyForLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - For-Statement was found:" + + System.lineSeparator() + " - Between line 8 (column 3) and line 10 (column 3)" + + System.lineSeparator() + " - For-Each-Statement was found:" + + System.lineSeparator() + + " - Between line 14 (column 3) and line 16 (column 3)")); + } + } + + @Nested + @DisplayName("Any-While-Loop-Test-Tests") + class AnyWhileLoopTestTests { + + @TestTest + void test_testHasBelowNoAnyWhileLoop_Success() { + String testHasBelowNoAnyWhileLoop_Success = "testHasBelowNoAnyWhileLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyWhileLoop_Success)); + } + + @TestTest + void test_testHasBelowNoAnyWhileLoop_Fail() { + String testHasBelowNoAnyWhileLoop_Fail = "testHasBelowNoAnyWhileLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoAnyWhileLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - While-Statement was found:" + + System.lineSeparator() + " - Between line 21 (column 3) and line 24 (column 3)" + + System.lineSeparator() + " - Do-While-Statement was found:" + + System.lineSeparator() + + " - Between line 29 (column 3) and line 32 (column 18)")); + } + } + + @Nested + @DisplayName("Any-Loop-Test-Tests") + class AnyLoopTestTests { + + @TestTest + void test_testHasBelowNoAnyLoop_Success() { + String testHasBelowNoAnyLoop_Success = "testHasBelowNoAnyLoop_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyLoop_Success)); + } + + @TestTest + void test_testHasBelowNoAnyLoop_Fail() { + String testHasBelowNoAnyLoop_Fail = "testHasBelowNoAnyLoop_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoAnyLoop_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "loops", "yes", + "ClassWithAnyKindsOfLoops.java") + + ":" + System.lineSeparator() + " - For-Statement was found:" + + System.lineSeparator() + " - Between line 8 (column 3) and line 10 (column 3)" + + System.lineSeparator() + " - For-Each-Statement was found:" + + System.lineSeparator() + " - Between line 14 (column 3) and line 16 (column 3)" + + System.lineSeparator() + " - While-Statement was found:" + System.lineSeparator() + + " - Between line 21 (column 3) and line 24 (column 3)" + System.lineSeparator() + + " - Do-While-Statement was found:" + System.lineSeparator() + + " - Between line 29 (column 3) and line 32 (column 18)")); + } + } + + @Nested + @DisplayName("If-Statement-Test-Tests") + class IfStatementTestTests { + + @TestTest + void test_testHasBelowNoIfStatement_Success() { + String testHasBelowNoIfConditional_Success = "testHasBelowNoIfStatement_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoIfConditional_Success)); + } + + @TestTest + void test_testHasBelowNoIfStatement_Fail() { + String testHasBelowNoIfConditional_Fail = "testHasBelowNoIfStatement_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoIfConditional_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "conditionals", "yes", + "ClassWithAnyKindsOfConditionals.java") + + ":" + System.lineSeparator() + " - If-Statement was found:" + + System.lineSeparator() + " - Between line 7 (column 3) and line 13 (column 3)" + + System.lineSeparator() + + " - Between line 9 (column 10) and line 13 (column 3)")); + } + } + + @Nested + @DisplayName("If-Expression-Test-Tests") + class IfExpressionTestTests { + + @TestTest + void test_testHasBelowNoIfExpression_Success() { + String testHasBelowNoIfConditional_Success = "testHasBelowNoIfExpression_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoIfConditional_Success)); + } + + @TestTest + void test_testHasBelowNoIfExpression_Fail() { + String testHasBelowNoIfConditional_Fail = "testHasBelowNoIfExpression_Fail"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testHasBelowNoIfConditional_Fail, + AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", "testuser", + "subject", "structural", "astTestFiles", "conditionals", "yes", + "ClassWithAnyKindsOfConditionals.java") + + ":" + System.lineSeparator() + " - If-Expression was found:" + System.lineSeparator() + + " - Between line 18 (column 22) and line 18 (column 64)" + System.lineSeparator() + + " - Between line 18 (column 42) and line 18 (column 63)")); + } + } + + @Nested + @DisplayName("Switch-Statement-Test-Tests") + class SwitchStatementTestTests { + + @TestTest + void test_testHasBelowNoSwitchStatement_Success() { + String testHasBelowNoSwitchConditional_Success = "testHasBelowNoSwitchStatement_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoSwitchConditional_Success)); + } + + @TestTest + void test_testHasBelowNoSwitchStatement_Fail() { + String testHasBelowNoSwitchConditional_Fail = "testHasBelowNoSwitchStatement_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoSwitchConditional_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "conditionals", "yes", + "ClassWithAnyKindsOfConditionals.java") + + ":" + System.lineSeparator() + " - Switch-Statement was found:" + + System.lineSeparator() + + " - Between line 23 (column 3) and line 33 (column 3)")); + } + } + + @Nested + @DisplayName("Switch-Expression-Test-Tests") + class SwitchExpressionTestTests { + + @TestTest + void test_testHasBelowNoSwitchExpression_Success() { + String testHasBelowNoSwitchConditional_Success = "testHasBelowNoSwitchExpression_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoSwitchConditional_Success)); + } + + @TestTest + void test_testHasBelowNoSwitchExpression_Fail() { + String testHasBelowNoSwitchConditional_Fail = "testHasBelowNoSwitchExpression_Fail"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testHasBelowNoSwitchConditional_Fail, + AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "resources", "de", "tum", "in", "test", "integration", "testuser", + "javaClassesWithUnsupportedFeatures", "conditionals", "yes", + "ClassWithAnyKindsOfUnsupportedConditionals.java") + + ":" + System.lineSeparator() + " - Switch-Expression was found:" + System.lineSeparator() + + " - Between line 38 (column 28) and line 43 (column 9)")); + } + } + + @Nested + @DisplayName("Any-If-Test-Tests") + class AnyIfTestTests { + + @TestTest + void test_testHasBelowNoAnyIf_Success() { + String testHasBelowNoAnyClass_Success = "testHasBelowNoAnyIf_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyClass_Success)); + } + + @TestTest + void test_testHasBelowNoAnyIf_Fail() { + String testHasBelowNoAnyClass_Fail = "testHasBelowNoAnyIf_Fail"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testHasBelowNoAnyClass_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", "testuser", + "subject", "structural", "astTestFiles", "conditionals", "yes", + "ClassWithAnyKindsOfConditionals.java") + + ":" + System.lineSeparator() + " - If-Statement was found:" + System.lineSeparator() + + " - Between line 7 (column 3) and line 13 (column 3)" + System.lineSeparator() + + " - Between line 9 (column 10) and line 13 (column 3)" + System.lineSeparator() + + " - If-Expression was found:" + System.lineSeparator() + + " - Between line 18 (column 22) and line 18 (column 64)" + System.lineSeparator() + + " - Between line 18 (column 42) and line 18 (column 63)")); + } + } + + @Nested + @DisplayName("Any-Switch-Test-Tests") + class AnySwitchTestTests { + + @TestTest + void test_testHasBelowNoAnySwitch_Success() { + String testHasBelowNoAnyClass_Success = "testHasBelowNoAnySwitch_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyClass_Success)); + } + + @TestTest + void test_testHasBelowNoAnySwitch_Fail() { + String testHasBelowNoAnyClass_Fail = "testHasBelowNoAnySwitch_Fail"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testHasBelowNoAnyClass_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "resources", "de", "tum", "in", "test", "integration", "testuser", + "javaClassesWithUnsupportedFeatures", "conditionals", "yes", + "ClassWithAnyKindsOfUnsupportedConditionals.java") + + ":" + System.lineSeparator() + " - Switch-Statement was found:" + System.lineSeparator() + + " - Between line 23 (column 9) and line 33 (column 9)" + System.lineSeparator() + + " - Switch-Expression was found:" + System.lineSeparator() + + " - Between line 38 (column 28) and line 43 (column 9)")); + } + } + + @Nested + @DisplayName("Any-Conditional-Test-Tests") + class AnyConditionalTestTests { + + @TestTest + void test_testHasBelowNoAnyConditional_Success() { + String testHasBelowNoAnyConditional_Success = "testHasBelowNoAnyConditional_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyConditional_Success)); + } + + @TestTest + void test_testHasBelowNoAnyConditional_Fail() { + String testHasBelowNoAnyConditional_Fail = "testHasBelowNoAnyConditional_Fail"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testHasBelowNoAnyConditional_Fail, + AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "resources", "de", "tum", "in", "test", "integration", "testuser", + "javaClassesWithUnsupportedFeatures", "conditionals", "yes", + "ClassWithAnyKindsOfUnsupportedConditionals.java") + + ":" + System.lineSeparator() + " - If-Statement was found:" + System.lineSeparator() + + " - Between line 7 (column 9) and line 13 (column 9)" + System.lineSeparator() + + " - Between line 9 (column 16) and line 13 (column 9)" + System.lineSeparator() + + " - If-Expression was found:" + System.lineSeparator() + + " - Between line 18 (column 28) and line 18 (column 70)" + System.lineSeparator() + + " - Between line 18 (column 48) and line 18 (column 69)" + System.lineSeparator() + + " - Switch-Statement was found:" + System.lineSeparator() + + " - Between line 23 (column 9) and line 33 (column 9)" + System.lineSeparator() + + " - Switch-Expression was found:" + System.lineSeparator() + + " - Between line 38 (column 28) and line 43 (column 9)" + + )); + } + } + + @Nested + @DisplayName("Class-Test-Tests") + class ClassTestTests { + + @TestTest + void test_testHasBelowNoClass_Success() { + String testHasBelowNoClass_Success = "testHasBelowNoClass_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoClass_Success)); + } + + @TestTest + void test_testHasBelowNoClass_Fail() { + String testHasBelowNoClass_Fail = "testHasBelowNoClass_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoClass_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "classes", "yes", + "ClassWithAnyKindsOfClasses.java") + + ":" + System.lineSeparator() + " - Local-Class-Statement was found:" + + System.lineSeparator() + " - Between line 6 (column 3) and line 7 (column 3)")); + } + } + + @Nested + @DisplayName("Record-Test-Tests") + class RecordTestTests { + + @TestTest + void test_testHasBelowNoRecord_Success() { + String testHasBelowNoRecord_Success = "testHasBelowNoRecord_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoRecord_Success)); + } + + @TestTest + void test_testHasBelowNoRecord_Fail() { + String testHasBelowNoRecord_Fail = "testHasBelowNoRecord_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoRecord_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "resources", "de", "tum", "in", "test", "integration", + "testuser", "javaClassesWithUnsupportedFeatures", "classes", "yes", + "ClassWithAnyKindsOfUnsupportedClasses.java") + + ":" + System.lineSeparator() + " - Local-Record-Statement was found:" + + System.lineSeparator() + + " - Between line 11 (column 9) and line 12 (column 9)")); + } + } + + @Nested + @DisplayName("Any-Class-Test-Tests") + class AnyClassTestTests { + + @TestTest + void test_testHasBelowNoAnyClass_Success() { + String testHasBelowNoClass_Success = "testHasBelowNoAnyClass_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoClass_Success)); + } + + @TestTest + void test_testHasBelowNoAnyClass_Fail() { + String testHasBelowNoClass_Fail = "testHasBelowNoAnyClass_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoClass_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "resources", "de", "tum", "in", "test", "integration", + "testuser", "javaClassesWithUnsupportedFeatures", "classes", "yes", + "ClassWithAnyKindsOfUnsupportedClasses.java") + + ":" + System.lineSeparator() + " - Local-Class-Statement was found:" + + System.lineSeparator() + " - Between line 6 (column 9) and line 7 (column 9)" + + System.lineSeparator() + " - Local-Record-Statement was found:" + + System.lineSeparator() + + " - Between line 11 (column 9) and line 12 (column 9)")); + } + } + + @Nested + @DisplayName("Assert-Exception-Handling-Test-Tests") + class AssertExceptionHandlingTestTests { + + @TestTest + void test_testHasBelowNoAssertExceptionHandling_Success() { + String testHasBelowNoAssertExceptionHandling_Success = "testHasBelowNoAssertExceptionHandling_Success"; + tests.assertThatEvents().haveExactly(1, + finishedSuccessfully(testHasBelowNoAssertExceptionHandling_Success)); + } + + @TestTest + void test_testHasBelowNoAssertExceptionHandling_Fail() { + String testHasBelowNoAssertExceptionHandling_Fail = "testHasBelowNoAssertExceptionHandling_Fail"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testHasBelowNoAssertExceptionHandling_Fail, + AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", "testuser", + "subject", "structural", "astTestFiles", "exceptionHandlings", "yes", + "ClassWithAnyKindsOfExceptionHandlings.java") + + ":" + System.lineSeparator() + " - Assert-Statement was found:" + System.lineSeparator() + + " - Between line 6 (column 3) and line 6 (column 16)")); + } + } + + @Nested + @DisplayName("Throw-Exception-Handling-Test-Tests") + class ThrowExceptionHandlingTestTests { + + @TestTest + void test_testHasBelowNoThrowExceptionHandling_Success() { + String testHasBelowNoThrowExceptionHandling_Success = "testHasBelowNoThrowExceptionHandling_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoThrowExceptionHandling_Success)); + } + + @TestTest + void test_testHasBelowNoThrowExceptionHandling_Fail() { + String testHasBelowNoThrowExceptionHandling_Fail = "testHasBelowNoThrowExceptionHandling_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoThrowExceptionHandling_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "exceptionHandlings", + "yes", "ClassWithAnyKindsOfExceptionHandlings.java") + + ":" + System.lineSeparator() + " - Throw-Statement was found:" + + System.lineSeparator() + + " - Between line 10 (column 3) and line 10 (column 54)")); + } + } + + @Nested + @DisplayName("Catch-Exception-Handling-Test-Tests") + class CatchExceptionHandlingTestTests { + + @TestTest + void test_testHasBelowNoCatchExceptionHandling_Success() { + String testHasBelowNoCatchExceptionHandling_Success = "testHasBelowNoCatchExceptionHandling_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoCatchExceptionHandling_Success)); + } + + @TestTest + void test_testHasBelowNoCatchExceptionHandling_Fail() { + String testHasBelowNoCatchExceptionHandling_Fail = "testHasBelowNoCatchExceptionHandling_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoCatchExceptionHandling_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "exceptionHandlings", + "yes", "ClassWithAnyKindsOfExceptionHandlings.java") + + ":" + System.lineSeparator() + " - Catch-Statement was found:" + + System.lineSeparator() + + " - Between line 16 (column 5) and line 17 (column 3)")); + } + } + + @Nested + @DisplayName("Any-Exception-Handling-Test-Tests") + class AnyExceptionHandlingTestTests { + + @TestTest + void test_testHasBelowNoAnyExceptionHandling_Success() { + String testHasBelowNoAnyExceptionHandling_Success = "testHasBelowNoAnyExceptionHandling_Success"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testHasBelowNoAnyExceptionHandling_Success)); + } + + @TestTest + void test_testHasBelowNoAnyExceptionHandling_Fail() { + String testHasBelowNoAnyExceptionHandling_Fail = "testHasBelowNoAnyExceptionHandling_Fail"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testHasBelowNoAnyExceptionHandling_Fail, AssertionError.class, + "Unwanted statement found:" + System.lineSeparator() + " - In " + + Path.of("src", "test", "java", "de", "tum", "in", "test", "integration", + "testuser", "subject", "structural", "astTestFiles", "exceptionHandlings", + "yes", "ClassWithAnyKindsOfExceptionHandlings.java") + + ":" + System.lineSeparator() + " - Assert-Statement was found:" + + System.lineSeparator() + " - Between line 6 (column 3) and line 6 (column 16)" + + System.lineSeparator() + " - Throw-Statement was found:" + System.lineSeparator() + + " - Between line 10 (column 3) and line 10 (column 54)" + System.lineSeparator() + + " - Catch-Statement was found:" + System.lineSeparator() + + " - Between line 16 (column 5) and line 17 (column 3)")); + } + } + + @Nested + @DisplayName("PomXml-Test-Tests") + class PomXmlTestTests { + + @TestTest + void test_testPomXmlFileDoesExist() { + String testPomXmlFileDoesExist = "testPomXmlFileDoesExist"; + tests.assertThatEvents().haveExactly(1, finishedSuccessfully(testPomXmlFileDoesExist)); + } + } + + @Nested + @DisplayName("Error-Test-Tests") + class ErrorTestTests { + + @TestTest + void test_testPathDoesNotExist() { + String testPathDoesNotExist = "testPathDoesNotExist"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testPathDoesNotExist, AssertionError.class, + "The source directory " + Path.of("this/path/does/not/exist") + " does not exist")); + } + + @TestTest + void test_testPathIsNull() { + String testPathIsNull = "testPathIsNull"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testPathIsNull, NullPointerException.class, + "The given source path must not be null.")); + } + + @TestTest + void test_testPackageDoesNotExist() { + String testPackageDoesNotExist = "testPackageDoesNotExist"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testPackageDoesNotExist, AssertionError.class, "The source directory " + + Path.of("src/test/java/this/package/name/does/not/exist") + " does not exist")); + } + + @TestTest + void test_testPackageIsNull() { + String testPackageIsNull = "testPackageIsNull"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testPackageIsNull, NullPointerException.class, + "The package name must not be null.")); + } + + @TestTest + void test_testBuildGradleFileDoesNotExist() { + String testBuildGradleFileDoesNotExist = "testBuildGradleFileDoesNotExist"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testBuildGradleFileDoesNotExist, AssertionError.class, + "Could not find project sources folder. Make sure the build file is configured correctly." + + " If it is not located in the execution folder directly," + + " set the location using AresConfiguration methods.")); + } + + @TestTest + void test_testBuildGradleFileIsNull() { + String testBuildGradleFileIsNull = "testBuildGradleFileIsNull"; + tests.assertThatEvents().haveExactly(1, + testFailedWith(testBuildGradleFileIsNull, AssertionError.class, + "Could not find project sources folder. Make sure the build file is configured correctly." + + " If it is not located in the execution folder directly," + + " set the location using AresConfiguration methods.")); + } + + @TestTest + void test_testLevelIsNull() { + String testLevelIsNull = "testLevelIsNull"; + tests.assertThatEvents().haveExactly(1, testFailedWith(testLevelIsNull, AssertionError.class, + "The 'level' is not set. Please use UnwantedNodesAssert.withLanguageLevel(LanguageLevel).")); + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/StructuralTest.java b/src/test/java/de/tum/in/test/integration/StructuralTest.java index 9a3cb565..10768183 100644 --- a/src/test/java/de/tum/in/test/integration/StructuralTest.java +++ b/src/test/java/de/tum/in/test/integration/StructuralTest.java @@ -199,6 +199,7 @@ void test_invalidMaven() { @TestTest void test_noBuildToolFile() { tests.assertThatEvents().haveExactly(1, testFailedWith(noBuildToolFile, AssertionFailedError.class, - "[[ERROR] Could not find any build file. Contact your instructor.]")); + "[[ERROR] Could not find any build file. Contact your instructor., " + + "[ERROR] Could not retrieve source directory from project file. Contact your instructor.]")); } } 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 new file mode 100644 index 00000000..599836a6 --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/AstAssertionUser.java @@ -0,0 +1,439 @@ +package de.tum.in.test.integration.testuser; + +import java.io.IOException; +import java.nio.file.Path; + +import org.junit.jupiter.api.*; + +import com.github.javaparser.ParserConfiguration; + +import de.tum.in.test.api.*; +import de.tum.in.test.api.ast.asserting.UnwantedNodesAssert; +import de.tum.in.test.api.ast.type.*; +import de.tum.in.test.api.jupiter.Public; +import de.tum.in.test.api.localization.UseLocale; + +@Public +@UseLocale("en") +@StrictTimeout(5) +public class AstAssertionUser { + + private static final String BASE_PACKAGE = "de.tum.in.test.integration.testuser.subject.structural.astTestFiles"; + private static final Path UNSUPPORTED_LEVEL_BASE_PATH = Path.of("src", "test", "resources", "de", "tum", "in", + "test", "integration", "testuser", "javaClassesWithUnsupportedFeatures"); + + private String mavenOld; + private String gradleOld; + + @BeforeEach + void configureProjectBuildFile() { + mavenOld = AresConfiguration.getPomXmlPath(); + gradleOld = AresConfiguration.getBuildGradlePath(); + AresConfiguration.setPomXmlPath(null); + AresConfiguration.setBuildGradlePath("src/test/resources/de/tum/in/test/integration/testuser/build.gradle"); + } + + @AfterEach + void unconfigureProjectBuildFile() { + AresConfiguration.setPomXmlPath(mavenOld); + AresConfiguration.setBuildGradlePath(gradleOld); + } + + @Nested + @DisplayName("For-Loop-Tests") + class ForLoopTests { + @Test + void testHasBelowNoForLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.FORSTMT); + } + + @Test + void testHasBelowNoForLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.FORSTMT); + } + } + + @Nested + @DisplayName("For-Each-Loop-Tests") + class ForEachLoopTests { + @Test + void testHasBelowNoForEachLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.FOR_EACHSTMT); + } + + @Test + void testHasBelowNoForEachLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.FOR_EACHSTMT); + } + } + + @Nested + @DisplayName("While-Loop-Tests") + class WhileLoopTests { + @Test + void testHasBelowNoWhileLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.WHILESTMT); + } + + @Test + void testHasBelowNoWhileLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.WHILESTMT); + } + } + + @Nested + @DisplayName("Do-While-Loop-Tests") + class DoWhileLoopTests { + @Test + void testHasBelowNoDoWhileLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.DO_WHILESTMT); + } + + @Test + void testHasBelowNoDoWhileLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.DO_WHILESTMT); + } + } + + @Nested + @DisplayName("Any-For-Loop-Tests") + class AnyForLoopTests { + @Test + void testHasBelowNoAnyForLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY_FOR); + } + + @Test + void testHasBelowNoAnyForLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY_FOR); + } + } + + @Nested + @DisplayName("Any-While-Loop-Tests") + class AnyWhileLoopTests { + @Test + void testHasBelowNoAnyWhileLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY_WHILE); + } + + @Test + void testHasBelowNoAnyWhileLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY_WHILE); + } + } + + @Nested + @DisplayName("Any-Loop-Tests") + class AnyLoopTests { + @Test + void testHasBelowNoAnyLoop_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY); + } + + @Test + void testHasBelowNoAnyLoop_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY); + } + } + + @Nested + @DisplayName("If-Statement-Tests") + class IfStatementTests { + @Test + void testHasBelowNoIfStatement_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.IFSTMT); + } + + @Test + void testHasBelowNoIfStatement_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.IFSTMT); + } + } + + @Nested + @DisplayName("If-Expression-Tests") + class IfExpressionTests { + @Test + void testHasBelowNoIfExpression_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(ConditionalType.CONDITIONALEXPR); + } + + @Test + void testHasBelowNoIfExpression_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(ConditionalType.CONDITIONALEXPR); + } + } + + @Nested + @DisplayName("Switch-Statement-Tests") + class SwitchStatementTests { + @Test + void testHasBelowNoSwitchStatement_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.SWITCHSTMT); + } + + @Test + void testHasBelowNoSwitchStatement_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.SWITCHSTMT); + } + } + + @Nested + @DisplayName("Switch-Expression-Tests") + class SwitchExpressionTests { + @Test + void testHasBelowNoSwitchExpression_Success() throws IOException { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("conditionals", "no"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.SWITCHEXPR); + } + + @Test + void testHasBelowNoSwitchExpression_Fail() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("conditionals", "yes"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.SWITCHEXPR); + } + } + + @Nested + @DisplayName("Any-If-Tests") + class AnyIfTests { + @Test + void testHasBelowNoAnyIf_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.ANY_IF); + } + + @Test + void testHasBelowNoAnyIf_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".conditionals.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.ANY_IF); + } + } + + @Nested + @DisplayName("Any-Switch-Tests") + class AnySwitchTests { + @Test + void testHasBelowNoAnySwitch_Success() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("conditionals", "no"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.ANY_SWITCH); + } + + @Test + void testHasBelowNoAnySwitch_Fail() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("conditionals", "yes"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.ANY_SWITCH); + } + } + + @Nested + @DisplayName("Any-Conditional-Tests") + class AnyConditionalTests { + @Test + void testHasBelowNoAnyConditional_Success() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("conditionals", "no"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.ANY); + } + + @Test + void testHasBelowNoAnyConditional_Fail() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("conditionals", "yes"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ConditionalType.ANY); + } + } + + @Nested + @DisplayName("Class-Tests") + class ClassTests { + @Test + void testHasBelowNoClass_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".classes.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ClassType.CLASS); + } + + @Test + void testHasBelowNoClass_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".classes.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ClassType.CLASS); + } + } + + @Nested + @DisplayName("Record-Tests") + class RecordTests { + @Test + void testHasBelowNoRecord_Success() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("classes", "no"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ClassType.RECORD); + } + + @Test + void testHasBelowNoRecord_Fail() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("classes", "yes"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ClassType.RECORD); + } + } + + @Nested + @DisplayName("Any-Class-Tests") + class AnyClassTests { + @Test + void testHasBelowNoAnyClass_Success() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("classes", "no"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ClassType.ANY); + } + + @Test + void testHasBelowNoAnyClass_Fail() { + UnwantedNodesAssert.assertThatSourcesIn(UNSUPPORTED_LEVEL_BASE_PATH.resolve(Path.of("classes", "yes"))) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ClassType.ANY); + } + } + + @Nested + @DisplayName("Assert-Exception-Handling-Tests") + class AssertExceptionHandlingTests { + @Test + void testHasBelowNoAssertExceptionHandling_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.ASSERT); + } + + @Test + void testHasBelowNoAssertExceptionHandling_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.ASSERT); + } + } + + @Nested + @DisplayName("Throw-Exception-Handling-Tests") + class ThrowExceptionHandlingTests { + @Test + void testHasBelowNoThrowExceptionHandling_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.THROW); + } + + @Test + void testHasBelowNoThrowExceptionHandling_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.THROW); + } + } + + @Nested + @DisplayName("Catch-Exception-Handling-Tests") + class CatchExceptionHandlingTests { + @Test + void testHasBelowNoCatchExceptionHandling_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.CATCH); + } + + @Test + void testHasBelowNoCatchExceptionHandling_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.CATCH); + } + } + + @Nested + @DisplayName("Any-Exception-Handling-Tests") + class AnyExceptionHandlingTests { + @Test + void testHasBelowNoAnyExceptionHandling_Success() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.ANY); + } + + @Test + void testHasBelowNoAnyExceptionHandling_Fail() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".exceptionHandlings.yes") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(ExceptionHandlingType.ANY); + } + } + + @Nested + @DisplayName("PomXml-Tests") + class PomXmlTests { + @Test + void testPomXmlFileDoesExist() { + AresConfiguration.setBuildGradlePath(null); + AresConfiguration.setPomXmlPath("src/test/resources/de/tum/in/test/integration/testuser/pom.xml"); + UnwantedNodesAssert.assertThatProjectSources().withinPackage(BASE_PACKAGE + ".loops.no") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.FORSTMT); + } + } + + @Nested + @DisplayName("Error-Tests") + class ErrorTests { + + @Test + void testPathDoesNotExist() { + UnwantedNodesAssert.assertThatSourcesIn(Path.of("this", "path", "does", "not", "exist")) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY); + } + + @Test + void testPathIsNull() { + UnwantedNodesAssert.assertThatSourcesIn(null).withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(LoopType.ANY); + } + + @Test + void testPackageDoesNotExist() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage("this.package.name.does.not.exist") + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY); + } + + @Test + void testPackageIsNull() { + UnwantedNodesAssert.assertThatProjectSources().withinPackage(null) + .withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17).hasNo(LoopType.ANY); + } + + @Test + void testBuildGradleFileDoesNotExist() { + AresConfiguration.setBuildGradlePath("does/not/exist/build.bradle"); + UnwantedNodesAssert.assertThatProjectSources().withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(LoopType.ANY); + } + + @Test + void testBuildGradleFileIsNull() { + AresConfiguration.setBuildGradlePath(null); + UnwantedNodesAssert.assertThatProjectSources().withLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17) + .hasNo(LoopType.ANY); + } + + @Test + void testLevelIsNull() { + UnwantedNodesAssert.assertThatProjectSources().hasNo(LoopType.ANY); + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/StructuralUser.java b/src/test/java/de/tum/in/test/integration/testuser/StructuralUser.java index 7c447ea6..0e1c1790 100644 --- a/src/test/java/de/tum/in/test/integration/testuser/StructuralUser.java +++ b/src/test/java/de/tum/in/test/integration/testuser/StructuralUser.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.fail; import java.net.URISyntaxException; +import java.util.List; import org.junit.jupiter.api.*; import org.slf4j.LoggerFactory; @@ -17,11 +18,13 @@ import de.tum.in.test.api.localization.UseLocale; import de.tum.in.test.api.structural.*; import de.tum.in.test.api.structural.testutils.ClassNameScanner; +import de.tum.in.test.api.util.ProjectSourcesFinder; @Public @UseLocale("en") @StrictTimeout(10) @WhitelistPath("") +@SuppressWarnings("deprecation") public class StructuralUser { private static final String TESTUSER_POM_XML = "src/test/resources/de/tum/in/test/integration/testuser/pom.xml"; @@ -50,18 +53,19 @@ void setupTest() { @Nested class InvalidConfigurations { - private final Logger logger = ((Logger) LoggerFactory.getLogger(ClassNameScanner.class)); + private final List loggers = List.of((Logger) LoggerFactory.getLogger(ProjectSourcesFinder.class), + (Logger) LoggerFactory.getLogger(ClassNameScanner.class)); private final ListAppender logs = new ListAppender<>(); @BeforeEach - void addLogger() { - logger.addAppender(logs); + void addLoggers() { + loggers.forEach(logger -> logger.addAppender(logs)); logs.start(); } @AfterEach - void removeLogger() { - logger.detachAppender(logs); + void removeLoggers() { + loggers.forEach(logger -> logger.detachAppender(logs)); } @Test diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/classes/no/ClassWithNoKindsOfClasses.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/classes/no/ClassWithNoKindsOfClasses.java new file mode 100644 index 00000000..a0dea144 --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/classes/no/ClassWithNoKindsOfClasses.java @@ -0,0 +1,85 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.classes.no; + +import java.util.stream.IntStream; + +public class ClassWithNoKindsOfClasses { + + 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 forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public void forEachLoop() { + for (int integer : new int[] { 3, 3, 3 }) { + System.out.println("Hello World"); + } + } + + public void whileLoop() { + int i = 0; + while (i < 3) { + System.out.println("Hello World"); + i++; + } + } + + public void doWhileLoop() { + int i = 0; + do { + System.out.println("Hello World"); + i++; + } while (i < 3); + } + + public void forEachStream() { + IntStream.range(0, 3).mapToObj((int i) -> "Hello World").forEach(System.out::println); + } + + 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) { + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/classes/yes/ClassWithAnyKindsOfClasses.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/classes/yes/ClassWithAnyKindsOfClasses.java new file mode 100644 index 00000000..a054863d --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/classes/yes/ClassWithAnyKindsOfClasses.java @@ -0,0 +1,9 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.classes.yes; + +public class ClassWithAnyKindsOfClasses { + + void localClassContainingFunction() { + class localClass { + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/conditionals/no/ClassWithNoKindsOfConditionals.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/conditionals/no/ClassWithNoKindsOfConditionals.java new file mode 100644 index 00000000..ec4b6114 --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/conditionals/no/ClassWithNoKindsOfConditionals.java @@ -0,0 +1,58 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.conditionals.no; + +import java.util.stream.IntStream; + +public class ClassWithNoKindsOfConditionals { + + public void forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public void forEachLoop() { + for (int integer : new int[] { 3, 3, 3 }) { + System.out.println("Hello World"); + } + } + + public void whileLoop() { + int i = 0; + while (i < 3) { + System.out.println("Hello World"); + i++; + } + } + + public void doWhileLoop() { + int i = 0; + do { + System.out.println("Hello World"); + i++; + } while (i < 3); + } + + public void forEachStream() { + IntStream.range(0, 3).mapToObj((int i) -> "Hello World").forEach(System.out::println); + } + + 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) { + } + } + + void localClassContainingFunction() { + class localClass { + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/conditionals/yes/ClassWithAnyKindsOfConditionals.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/conditionals/yes/ClassWithAnyKindsOfConditionals.java new file mode 100644 index 00000000..dade5b0a --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/conditionals/yes/ClassWithAnyKindsOfConditionals.java @@ -0,0 +1,36 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.conditionals.yes; + +public class ClassWithAnyKindsOfConditionals { + + 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); + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/exceptionHandlings/no/ClassWithNoKindsOfExceptionHandlings.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/exceptionHandlings/no/ClassWithNoKindsOfExceptionHandlings.java new file mode 100644 index 00000000..33678a0b --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/exceptionHandlings/no/ClassWithNoKindsOfExceptionHandlings.java @@ -0,0 +1,75 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.exceptionHandlings.no; + +import java.util.stream.IntStream; + +public class ClassWithNoKindsOfExceptionHandlings { + + 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 forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public void forEachLoop() { + for (int integer : new int[] { 3, 3, 3 }) { + System.out.println("Hello World"); + } + } + + public void whileLoop() { + int i = 0; + while (i < 3) { + System.out.println("Hello World"); + i++; + } + } + + public void doWhileLoop() { + int i = 0; + do { + System.out.println("Hello World"); + i++; + } while (i < 3); + } + + public void forEachStream() { + IntStream.range(0, 3).mapToObj((int i) -> "Hello World").forEach(System.out::println); + } + + void localClassContainingFunction() { + class localClass { + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/exceptionHandlings/yes/ClassWithAnyKindsOfExceptionHandlings.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/exceptionHandlings/yes/ClassWithAnyKindsOfExceptionHandlings.java new file mode 100644 index 00000000..751e8765 --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/exceptionHandlings/yes/ClassWithAnyKindsOfExceptionHandlings.java @@ -0,0 +1,19 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.exceptionHandlings.yes; + +public class ClassWithAnyKindsOfExceptionHandlings { + + 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) { + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/loops/no/ClassWithNoKindsOfLoops.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/loops/no/ClassWithNoKindsOfLoops.java new file mode 100644 index 00000000..d1f7876f --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/loops/no/ClassWithNoKindsOfLoops.java @@ -0,0 +1,56 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.loops.no; + +public class ClassWithNoKindsOfLoops { + + 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) { + } + } + + void localClassContainingFunction() { + class localClass { + } + } +} diff --git a/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/loops/yes/ClassWithAnyKindsOfLoops.java b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/loops/yes/ClassWithAnyKindsOfLoops.java new file mode 100644 index 00000000..26cb737b --- /dev/null +++ b/src/test/java/de/tum/in/test/integration/testuser/subject/structural/astTestFiles/loops/yes/ClassWithAnyKindsOfLoops.java @@ -0,0 +1,38 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.loops.yes; + +import java.util.stream.IntStream; + +public class ClassWithAnyKindsOfLoops { + + public void forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public void forEachLoop() { + for (int integer : new int[] { 3, 3, 3 }) { + System.out.println("Hello World"); + } + } + + public void whileLoop() { + int i = 0; + while (i < 3) { + System.out.println("Hello World"); + i++; + } + } + + public void doWhileLoop() { + int i = 0; + do { + System.out.println("Hello World"); + i++; + } while (i < 3); + } + + public void forEachStream() { + IntStream.range(0, 3).mapToObj((int i) -> "Hello World").forEach(System.out::println); + } +} diff --git a/src/test/java/de/tum/in/test/testutilities/TestUserExtension.java b/src/test/java/de/tum/in/test/testutilities/TestUserExtension.java index fe4c27c0..523eb10a 100644 --- a/src/test/java/de/tum/in/test/testutilities/TestUserExtension.java +++ b/src/test/java/de/tum/in/test/testutilities/TestUserExtension.java @@ -18,6 +18,8 @@ class TestUserExtension implements BeforeAllCallback, TestInstancePostProcessor, @Override public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception { + if (isNotApplicableTo(context)) + return; var testClass = testInstance.getClass(); var testResults = getTestResults(context); var varHandles = findSupportedVarHandles(testClass, context, false); @@ -28,6 +30,8 @@ public void postProcessTestInstance(Object testInstance, ExtensionContext contex @Override public void beforeAll(ExtensionContext context) throws Exception { + if (isNotApplicableTo(context)) + return; var optionalAnnotation = AnnotationSupport.findAnnotation(context.getElement(), UserBased.class); if (optionalAnnotation.isEmpty()) fail("No annotated element found for @UserBased"); @@ -98,4 +102,8 @@ private boolean supports(AnnotatedElement element, Class type, ExtensionConte private Object getTestResults(ExtensionContext context) { return getStore(context).get(TEST_RESULTS); } + + private static boolean isNotApplicableTo(ExtensionContext context) { + return context.getTestClass().map(Class::isMemberClass).orElse(true); + } } diff --git a/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/classes/no/ClassWithNoKindsOfUnsupportedClasses.java b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/classes/no/ClassWithNoKindsOfUnsupportedClasses.java new file mode 100644 index 00000000..8b3cfb4a --- /dev/null +++ b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/classes/no/ClassWithNoKindsOfUnsupportedClasses.java @@ -0,0 +1,95 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.classes.no; + +import java.util.stream.IntStream; + +public class ClassWithNoKindsOfUnsupportedClasses { + + 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 switchExpression() { + System.out.println(switch (new + Random().nextInt(3)) { + case 1 -> "Hello"; + case 0 -> "World"; + default -> "!"; + }); + } + + public void forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public void forEachLoop() { + for (int integer : new int[]{3, 3, + 3}) { + System.out.println("Hello World"); + } + } + + public void whileLoop() { + int i = 0; + while (i < 3) { + System.out.println("Hello World"); + i++; + } + } + + public void doWhileLoop() { + int i = 0; + do { + System.out.println("Hello World"); + i++; + } while (i < 3); + } + + public void forEachStream() { + IntStream.range(0, 3).mapToObj((int i) -> "Hello World").forEach(System.out::println); + } + + 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) { + } + } +} diff --git a/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/classes/yes/ClassWithAnyKindsOfUnsupportedClasses.java b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/classes/yes/ClassWithAnyKindsOfUnsupportedClasses.java new file mode 100644 index 00000000..ce2eac55 --- /dev/null +++ b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/classes/yes/ClassWithAnyKindsOfUnsupportedClasses.java @@ -0,0 +1,14 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.classes.yes; + +public class ClassWithAnyKindsOfUnsupportedClasses { + + void localClassContainingFunction() { + class localClass { + } + } + + void localRecordContainingFunction() { + record localRecord (String id){ + } + } +} diff --git a/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/conditionals/no/ClassWithNoKindsOfUnsupportedConditionals.java b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/conditionals/no/ClassWithNoKindsOfUnsupportedConditionals.java new file mode 100644 index 00000000..aa981452 --- /dev/null +++ b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/conditionals/no/ClassWithNoKindsOfUnsupportedConditionals.java @@ -0,0 +1,64 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.conditionals.no; + +import java.util.stream.IntStream; + +public class ClassWithNoKindsOfUnsupportedConditionals { + + public void forLoop() { + for (int i = 0; i < 3; i++) { + System.out.println("Hello World"); + } + } + + public void forEachLoop() { + for (int integer : new int[]{3, 3, + 3}) { + System.out.println("Hello World"); + } + } + + public void whileLoop() { + int i = 0; + while (i < 3) { + System.out.println("Hello World"); + i++; + } + } + + public void doWhileLoop() { + int i = 0; + do { + System.out.println("Hello World"); + i++; + } while (i < 3); + } + + public void forEachStream() { + IntStream.range(0, 3).mapToObj((int i) -> "Hello World").forEach(System.out::println); + } + + 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) { + } + } + + void localClassContainingFunction() { + class localClass { + } + } + + void localRecordContainingFunction() { + record localRecord (String id){ + } + } +} diff --git a/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/conditionals/yes/ClassWithAnyKindsOfUnsupportedConditionals.java b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/conditionals/yes/ClassWithAnyKindsOfUnsupportedConditionals.java new file mode 100644 index 00000000..21136ae4 --- /dev/null +++ b/src/test/resources/de/tum/in/test/integration/testuser/javaClassesWithUnsupportedFeatures/conditionals/yes/ClassWithAnyKindsOfUnsupportedConditionals.java @@ -0,0 +1,45 @@ +package de.tum.in.test.integration.testuser.subject.structural.astTestFiles.conditionals.yes; + +public class ClassWithAnyKindsOfUnsupportedConditionals { + + 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 switchExpression() { + System.out.println(switch (new + Random().nextInt(3)) { + case 1 -> "Hello"; + case 0 -> "World"; + default -> "!"; + }); + } +}