diff --git a/.gitattributes b/.gitattributes index 282705fb..c6a09df3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ * text=auto *.bat text eol=crlf + +*.approved.* binary diff --git a/.gitignore b/.gitignore index 48f830ba..8f73c833 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,7 @@ build .project .classpath +src/test/**/*.received.txt + tests/**/*/analysis.json tests/**/*/tags.json diff --git a/README.md b/README.md index ff3d31ef..68cadd39 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,10 @@ Then, run the Java analyzer using `build/libs/java-analyzer.jar`. For example, to analyze a solution for the `leap` exercise, run: ```sh -java -jar build/libs/java-analyzer.jar leap /path/to/leap/ /path/to/output/folder/ +java -jar build/libs/java-analyzer.jar leap /path/to/leap /path/to/output/folder ``` -The analyzer output is written to `analysis.json` and `tags.json` in `/path/to/output/folder/`. +The analyzer output is written to `analysis.json` and `tags.json` in `/path/to/output/folder`. ### Running with Docker @@ -39,10 +39,10 @@ Then, run the image and mount the directory of the solution to analyze. For example, to analyze a solution for the `leap` exercise located at `~/exercism/java/leap`, run: ```sh -docker run -v /path/to/leap:/input -v /path/to/output/folder:/output exercism/java-analyzer leap /input/ /output/ +docker run -v /path/to/leap:/input -v /path/to/output/folder:/output exercism/java-analyzer leap /input /output ``` -The analyzer output is written to `analysis.json` and `tags.json` in `/path/to/output/folder/`. +The analyzer output is written to `analysis.json` and `tags.json` in `/path/to/output/folder`. ## Tests diff --git a/bin/run-in-docker.sh b/bin/run-in-docker.sh index 786e8da3..977c7745 100755 --- a/bin/run-in-docker.sh +++ b/bin/run-in-docker.sh @@ -14,11 +14,11 @@ # The test results are formatted according to the specifications at https://github.com/exercism/docs/blob/main/building/tooling/analyzers/interface.md # Example: -# ./bin/run-in-docker.sh two-fer /absolute/path/to/two-fer/solution/folder/ /absolute/path/to/output/directory/ +# ./bin/run-in-docker.sh two-fer /absolute/path/to/two-fer/solution/folder /absolute/path/to/output/directory # If any required arguments is missing, print the usage and exit if [ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ]; then - echo "usage: ./bin/run-in-docker.sh exercise-slug /absolute/path/to/solution/folder/ /absolute/path/to/output/directory/" + echo "usage: ./bin/run-in-docker.sh exercise-slug /absolute/path/to/solution/folder /absolute/path/to/output/directory" exit 1 fi diff --git a/build.gradle b/build.gradle index 2e71ce4f..c23b157c 100644 --- a/build.gradle +++ b/build.gradle @@ -15,12 +15,13 @@ repositories { } dependencies { - implementation "org.json:json:20231013" + implementation "com.google.code.gson:gson:2.10.1" implementation "com.github.javaparser:javaparser-core:3.25.8" testImplementation platform("org.junit:junit-bom:5.10.1") testImplementation "org.junit.jupiter:junit-jupiter" testImplementation "org.assertj:assertj-core:3.25.2" + testImplementation "com.approvaltests:approvaltests:22.3.2" } shadowJar { diff --git a/src/main/java/analyzer/Analyzer.java b/src/main/java/analyzer/Analyzer.java index e8fb6fe8..57e0cce6 100644 --- a/src/main/java/analyzer/Analyzer.java +++ b/src/main/java/analyzer/Analyzer.java @@ -5,13 +5,13 @@ */ public interface Analyzer { /** - * Analyze the given solution and append analysis results to the given analysis. + * Analyze the given solution and append analysis results to the given output.. * The {@code analyze} method of each analyzer is invoked once for the whole submitted solution. * * @param solution The solution that should be analyzed. - * @param analysis The analysis instance used to collect results. + * @param output The output collector instance used to collect analyzer results. * This instance is shared across all analyzers, and should be used to add comments and tags, * or set a summary. */ - void analyze(Solution solution, Analysis analysis); + void analyze(Solution solution, OutputCollector output); } diff --git a/src/main/java/analyzer/AnalyzerCli.java b/src/main/java/analyzer/AnalyzerCli.java index 6f36d5fc..deb712fb 100644 --- a/src/main/java/analyzer/AnalyzerCli.java +++ b/src/main/java/analyzer/AnalyzerCli.java @@ -1,7 +1,6 @@ package analyzer; import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Path; @@ -10,13 +9,19 @@ * The CLI expects three arguments and is used like this: * *
- * java -jar java-analyzer.jar exercise-slug /path/to/input/ /path/to/output/
+ * java -jar java-analyzer.jar exercise-slug /path/to/input /path/to/output
  * 
*/ public class AnalyzerCli { - private static boolean isNotValidDirectory(String p) { - return !p.endsWith("/") || !new File(p).isDirectory(); + private static Path validateDirectory(String directory) { + var file = new File(directory); + + if (!file.exists() || !file.isDirectory()) { + throw new IllegalArgumentException("Not a valid directory: " + directory); + } + + return file.toPath(); } public static void main(String... args) throws IOException { @@ -25,26 +30,13 @@ public static void main(String... args) throws IOException { System.exit(-1); } - String slug = args[0]; - String inputDirectory = args[1]; - String outputDirectory = args[2]; - - if (isNotValidDirectory(inputDirectory)) { - System.err.println("Invalid input directory. Must be a valid directory and end with a slash."); - System.exit(-1); - } - if (isNotValidDirectory(outputDirectory)) { - System.err.println("Invalid output directory. Must be a valid directory and end with a slash."); - System.exit(-1); - } - - var solution = new SubmittedSolution(slug, Path.of(inputDirectory)); - var analysis = AnalyzerRoot.analyze(solution); + var slug = args[0]; + var inputDirectory = validateDirectory(args[1]); + var outputDirectory = validateDirectory(args[2]); - try (var analysisWriter = new FileWriter(Path.of(outputDirectory, "analysis.json").toFile()); - var tagsWriter = new FileWriter(Path.of(outputDirectory, "tags.json").toFile())) { - var output = new OutputWriter(analysisWriter, tagsWriter); - output.write(analysis); - } + var outputWriter = new OutputWriter(outputDirectory); + var solution = new SubmittedSolution(slug, inputDirectory); + var output = AnalyzerRoot.analyze(solution); + outputWriter.write(output); } } diff --git a/src/main/java/analyzer/AnalyzerRoot.java b/src/main/java/analyzer/AnalyzerRoot.java index 57772cb6..394ddfd3 100644 --- a/src/main/java/analyzer/AnalyzerRoot.java +++ b/src/main/java/analyzer/AnalyzerRoot.java @@ -23,20 +23,20 @@ private AnalyzerRoot() { * Perform the analysis of a solution. * * @param solution The solution being analyzed. - * @return The aggregated analysis of all applicable analyzers. + * @return The aggregated output of all applicable analyzers. */ - public static Analysis analyze(Solution solution) { - var analysis = new Analysis(); + public static Output analyze(Solution solution) { + var outputBuilder = new OutputBuilder(); for (Analyzer analyzer : createAnalyzers(solution.getSlug())) { - analyzer.analyze(solution, analysis); + analyzer.analyze(solution, outputBuilder); } - if (analysis.getComments().stream().anyMatch(x -> x.getType() != Comment.Type.CELEBRATORY)) { - analysis.addComment(new FeedbackRequest()); + if (outputBuilder.getComments().stream().anyMatch(x -> x.getType() != Comment.Type.CELEBRATORY)) { + outputBuilder.addComment(new FeedbackRequest()); } - return analysis; + return outputBuilder.build(); } private static List createAnalyzers(String slug) { diff --git a/src/main/java/analyzer/Output.java b/src/main/java/analyzer/Output.java new file mode 100644 index 00000000..0c76b176 --- /dev/null +++ b/src/main/java/analyzer/Output.java @@ -0,0 +1,12 @@ +package analyzer; + +import java.util.List; + +public record Output(Analysis analysis, Tags tags) { + + public record Analysis(String summary, List comments) { + } + + public record Tags(List tags) { + } +} diff --git a/src/main/java/analyzer/OutputBuilder.java b/src/main/java/analyzer/OutputBuilder.java new file mode 100644 index 00000000..54108b02 --- /dev/null +++ b/src/main/java/analyzer/OutputBuilder.java @@ -0,0 +1,49 @@ +package analyzer; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +class OutputBuilder implements OutputCollector { + private String summary; + private final Set comments = new LinkedHashSet<>(); + private final Set tags = new LinkedHashSet<>(); + + public String getSummary() { + return summary; + } + + public void setSummary(String summary) { + this.summary = summary; + } + + public List getComments() { + return List.copyOf(comments); + } + + public List getTags() { + return List.copyOf(tags); + } + + public void addComment(Comment comment) { + comments.add(comment); + } + + public void addTag(String tag) { + tags.add(tag); + } + + Output build() { + var sortedComments = this.comments.stream().sorted(OutputBuilder::compareCommentsByType).toList(); + var analysis = new Output.Analysis(this.summary, sortedComments); + var tags = new Output.Tags(List.copyOf(this.tags)); + return new Output(analysis, tags); + } + + private static int compareCommentsByType(Comment a, Comment b) { + var ordinalA = Optional.ofNullable(a.getType()).map(Comment.Type::ordinal).orElse(Integer.MAX_VALUE); + var ordinalB = Optional.ofNullable(b.getType()).map(Comment.Type::ordinal).orElse(Integer.MAX_VALUE); + return Integer.compare(ordinalA, ordinalB); + } +} diff --git a/src/main/java/analyzer/Analysis.java b/src/main/java/analyzer/OutputCollector.java similarity index 63% rename from src/main/java/analyzer/Analysis.java rename to src/main/java/analyzer/OutputCollector.java index c219bbf7..04f8de04 100644 --- a/src/main/java/analyzer/Analysis.java +++ b/src/main/java/analyzer/OutputCollector.java @@ -1,18 +1,13 @@ package analyzer; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; /** - * This class is used to collect analysis results in the form of comments, tags and an optional summary. + * This interface is used to collect analyzer output in the form of comments, tags and an optional summary. * * @see The analyzer interface in the Exercism documentation */ -public class Analysis { - private String summary; - private final Set comments = new LinkedHashSet<>(); - private final Set tags = new LinkedHashSet<>(); +public interface OutputCollector { /** * The summary is a short description of the complete analysis result. @@ -20,9 +15,7 @@ public class Analysis { * * @return The summary if set, {@code null} otherwise. */ - public String getSummary() { - return summary; - } + String getSummary(); /** * Set the summary of the analysis. @@ -31,9 +24,7 @@ public String getSummary() { * * @param summary The summary to set. */ - public void setSummary(String summary) { - this.summary = summary; - } + void setSummary(String summary); /** * Retrieve a copy of the comments added to this analysis. @@ -41,9 +32,7 @@ public void setSummary(String summary) { * * @return List of comments. */ - public List getComments() { - return List.copyOf(comments); - } + List getComments(); /** * Retrieve a copy of the tags added to this analysis. @@ -51,9 +40,7 @@ public List getComments() { * * @return List of tags. */ - public List getTags() { - return List.copyOf(tags); - } + List getTags(); /** * Add a new comment to the analysis. @@ -61,9 +48,7 @@ public List getTags() { * * @param comment The comment to add. */ - public void addComment(Comment comment) { - comments.add(comment); - } + void addComment(Comment comment); /** * Add a new tag to the analysis. @@ -71,7 +56,5 @@ public void addComment(Comment comment) { * * @param tag The tag to add. */ - public void addTag(String tag) { - tags.add(tag); - } + void addTag(String tag); } diff --git a/src/main/java/analyzer/OutputSerializer.java b/src/main/java/analyzer/OutputSerializer.java new file mode 100644 index 00000000..dd2a75cc --- /dev/null +++ b/src/main/java/analyzer/OutputSerializer.java @@ -0,0 +1,48 @@ +package analyzer; + +import com.google.gson.*; + +import java.lang.reflect.Type; +import java.util.Map; +import java.util.TreeMap; + +/** + * Serializer to convert the analyzer output to JSON. + * + * @see The analyzer interface in the Exercism documentation + */ +class OutputSerializer { + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(Comment.class, new CommentJsonSerializer()) + .setPrettyPrinting() + .create(); + + static String serialize(Output.Analysis analysis) { + return GSON.toJson(analysis); + } + + static String serialize(Output.Tags tags) { + return GSON.toJson(tags); + } + + private static class CommentJsonSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Comment comment, Type type, JsonSerializationContext jsonSerializationContext) { + var json = new JsonObject(); + json.addProperty("comment", comment.getKey()); + json.add("params", serializeParameters(comment.getParameters())); + + if (comment.getType() != null) { + json.addProperty("type", comment.getType().name().toLowerCase()); + } + + return json; + } + + private static JsonElement serializeParameters(Map parameters) { + var json = new JsonObject(); + new TreeMap<>(parameters).forEach(json::addProperty); + return json; + } + } +} diff --git a/src/main/java/analyzer/OutputWriter.java b/src/main/java/analyzer/OutputWriter.java index 7eeed5b9..65f91e9e 100644 --- a/src/main/java/analyzer/OutputWriter.java +++ b/src/main/java/analyzer/OutputWriter.java @@ -1,77 +1,37 @@ package analyzer; -import org.json.JSONObject; - +import java.io.FileWriter; import java.io.IOException; -import java.io.Writer; -import java.util.Optional; +import java.nio.file.Path; /** - * The {@link OutputWriter} converts the analysis result into JSON output and writes it to the writers passed to the constructor. + * The {@link OutputWriter} serializes the analyzer output and writes it files in the given output path. * * @see The analyzer interface in the Exercism documentation */ -public class OutputWriter { - private static final int JSON_INDENTATION = 2; - - private final Writer analysisWriter; - private final Writer tagsWriter; +class OutputWriter { + private final Path outputPath; - public OutputWriter(Writer analysisWriter, Writer tagsWriter) { - this.analysisWriter = analysisWriter; - this.tagsWriter = tagsWriter; + OutputWriter(Path outputPath) { + this.outputPath = outputPath; } - public void write(Analysis analysis) throws IOException { - writeAnalysis(analysis); - writeTags(analysis); + void write(Output output) throws IOException { + writeAnalysis(output.analysis()); + writeTags(output.tags()); } - private void writeAnalysis(Analysis analysis) throws IOException { - var json = new JSONObject(); - - if (analysis.getSummary() != null) { - json.put("summary", analysis.getSummary()); - } - - analysis.getComments() - .stream() - .sorted(OutputWriter::compareCommentsByType) - .forEachOrdered(comment -> json.append("comments", serialize(comment))); - - this.analysisWriter.write(json.toString(JSON_INDENTATION)); + private void writeAnalysis(Output.Analysis analysis) throws IOException { + write(OutputSerializer.serialize(analysis), this.outputPath.resolve("analysis.json")); } - private void writeTags(Analysis analysis) throws IOException { - var json = new JSONObject(); - for (String tag : analysis.getTags()) { - json.append("tags", tag); - } - - this.tagsWriter.write(json.toString(JSON_INDENTATION)); + private void writeTags(Output.Tags tags) throws IOException { + write(OutputSerializer.serialize(tags), this.outputPath.resolve("tags.json")); } - private static JSONObject serialize(Comment comment) { - var json = new JSONObject(); - json.put("comment", comment.getKey()); - - if (comment.getType() != null) { - json.put("type", comment.getType().name().toLowerCase()); - } - - if (comment.getParameters().isEmpty()) { - return json; + private void write(String contents, Path path) throws IOException { + try (var writer = new FileWriter(path.toFile())) { + writer.write(contents); } - - var paramsJson = new JSONObject(); - comment.getParameters().forEach(paramsJson::put); - json.put("params", paramsJson); - return json; - } - - private static int compareCommentsByType(Comment a, Comment b) { - var ordinalA = Optional.ofNullable(a.getType()).map(Comment.Type::ordinal).orElse(Integer.MAX_VALUE); - var ordinalB = Optional.ofNullable(b.getType()).map(Comment.Type::ordinal).orElse(Integer.MAX_VALUE); - return Integer.compare(ordinalA, ordinalB); } } diff --git a/src/main/java/analyzer/exercises/GlobalAnalyzer.java b/src/main/java/analyzer/exercises/GlobalAnalyzer.java index 6bdc0e6b..d2b45353 100644 --- a/src/main/java/analyzer/exercises/GlobalAnalyzer.java +++ b/src/main/java/analyzer/exercises/GlobalAnalyzer.java @@ -1,6 +1,6 @@ package analyzer.exercises; -import analyzer.Analysis; +import analyzer.OutputCollector; import analyzer.Analyzer; import analyzer.Solution; import analyzer.comments.AvoidPrintStatements; @@ -15,31 +15,31 @@ * such as whether a solution is using print statements or a static {@code main} method. * It extends from the {@link VoidVisitorAdapter} and uses the visitor pattern to traverse each compilation unit. */ -public class GlobalAnalyzer extends VoidVisitorAdapter implements Analyzer { +public class GlobalAnalyzer extends VoidVisitorAdapter implements Analyzer { @Override - public void analyze(Solution solution, Analysis analysis) { + public void analyze(Solution solution, OutputCollector output) { for (CompilationUnit compilationUnit : solution.getCompilationUnits()) { - compilationUnit.accept(this, analysis); + compilationUnit.accept(this, output); } } @Override - public void visit(MethodDeclaration node, Analysis analysis) { + public void visit(MethodDeclaration node, OutputCollector outputCollector) { if (isMainMethod(node)) { - analysis.addComment(new DoNotUseMainMethod()); + outputCollector.addComment(new DoNotUseMainMethod()); } - super.visit(node, analysis); + super.visit(node, outputCollector); } @Override - public void visit(MethodCallExpr node, Analysis analysis) { + public void visit(MethodCallExpr node, OutputCollector outputCollector) { if (isPrintStatement(node)) { - analysis.addComment(new AvoidPrintStatements()); + outputCollector.addComment(new AvoidPrintStatements()); } - super.visit(node, analysis); + super.visit(node, outputCollector); } private static boolean isMainMethod(MethodDeclaration node) { diff --git a/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java b/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java index b14106a8..11a43484 100644 --- a/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java +++ b/src/main/java/analyzer/exercises/hamming/HammingAnalyzer.java @@ -1,6 +1,6 @@ package analyzer.exercises.hamming; -import analyzer.Analysis; +import analyzer.OutputCollector; import analyzer.Analyzer; import analyzer.Solution; import analyzer.comments.ConstructorTooLong; @@ -17,37 +17,37 @@ public class HammingAnalyzer implements Analyzer { @Override - public void analyze(Solution solution, Analysis analysis) { + public void analyze(Solution solution, OutputCollector output) { HammingWalker walker = new HammingWalker(); solution.getCompilationUnits().forEach(cu -> cu.walk(ClassOrInterfaceDeclaration.class, walker)); if (walker.usesCharacterLiterals()) { - analysis.addComment(new AvoidCharacterLiterals()); + output.addComment(new AvoidCharacterLiterals()); return; } if (!walker.usesStringCharAtOrCodePointAt()) { - analysis.addComment(new MustUseStringCharAtOrCodePointAt()); + output.addComment(new MustUseStringCharAtOrCodePointAt()); return; } if (!walker.constructorMayCalculateDistance()) { - analysis.addComment(new CalculateDistanceInConstructor()); + output.addComment(new CalculateDistanceInConstructor()); } if (walker.shouldUseStreamFilterAndCount()) { - analysis.addComment(new ShouldUseStreamFilterAndCount()); + output.addComment(new ShouldUseStreamFilterAndCount()); } Set longConstructors = walker.getLongConstructors(); if (!longConstructors.isEmpty()) { - analysis.addComment(new ConstructorTooLong(longConstructors)); + output.addComment(new ConstructorTooLong(longConstructors)); } Set longMethods = walker.getLongMethods(); if (!longMethods.isEmpty()) { - analysis.addComment(new MethodTooLong(longMethods)); + output.addComment(new MethodTooLong(longMethods)); } } } diff --git a/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java b/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java index 855ccabe..3efd5fc7 100644 --- a/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java +++ b/src/main/java/analyzer/exercises/lasagna/LasagnaAnalyzer.java @@ -1,7 +1,7 @@ package analyzer.exercises.lasagna; -import analyzer.Analysis; import analyzer.Analyzer; +import analyzer.OutputCollector; import analyzer.Solution; import analyzer.comments.ExemplarSolution; import analyzer.comments.RemoveTodoComments; @@ -17,7 +17,7 @@ * * @see The lasagna exercise on the Java track */ -public class LasagnaAnalyzer extends VoidVisitorAdapter implements Analyzer { +public class LasagnaAnalyzer extends VoidVisitorAdapter implements Analyzer { private static final String EXERCISE_NAME = "Lasagna"; private static final String EXPECTED_MINUTES_IN_OVEN = "expectedMinutesInOven"; private static final String REMAINING_MINUTES_IN_OVEN = "remainingMinutesInOven"; @@ -25,29 +25,29 @@ public class LasagnaAnalyzer extends VoidVisitorAdapter implements Ana private static final String TOTAL_TIME_IN_MINUTES = "totalTimeInMinutes"; @Override - public void analyze(Solution solution, Analysis analysis) { + public void analyze(Solution solution, OutputCollector output) { for (CompilationUnit compilationUnit : solution.getCompilationUnits()) { - compilationUnit.accept(this, analysis); + compilationUnit.accept(this, output); } - if (analysis.getComments().isEmpty()) { - analysis.addComment(new ExemplarSolution(EXERCISE_NAME)); + if (output.getComments().isEmpty()) { + output.addComment(new ExemplarSolution(EXERCISE_NAME)); } } @Override - public void visit(MethodDeclaration node, Analysis analysis) { + public void visit(MethodDeclaration node, OutputCollector output) { if (node.getNameAsString().equals(REMAINING_MINUTES_IN_OVEN) && doesNotCallMethod(node, EXPECTED_MINUTES_IN_OVEN)) { - analysis.addComment(new ReuseCode(REMAINING_MINUTES_IN_OVEN, EXPECTED_MINUTES_IN_OVEN)); + output.addComment(new ReuseCode(REMAINING_MINUTES_IN_OVEN, EXPECTED_MINUTES_IN_OVEN)); } if (node.getNameAsString().equals(TOTAL_TIME_IN_MINUTES) && doesNotCallMethod(node, PREPARATION_TIME_IN_MINUTES)) { - analysis.addComment(new ReuseCode(TOTAL_TIME_IN_MINUTES, PREPARATION_TIME_IN_MINUTES)); + output.addComment(new ReuseCode(TOTAL_TIME_IN_MINUTES, PREPARATION_TIME_IN_MINUTES)); } - super.visit(node, analysis); + super.visit(node, output); } private static boolean doesNotCallMethod(MethodDeclaration node, String otherMethodName) { @@ -55,9 +55,9 @@ private static boolean doesNotCallMethod(MethodDeclaration node, String otherMet } @Override - public void visit(LineComment node, Analysis analysis) { + public void visit(LineComment node, OutputCollector output) { if (node.getContent().contains("TODO")) { - analysis.addComment(new RemoveTodoComments()); + output.addComment(new RemoveTodoComments()); } } } diff --git a/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java b/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java index e3830c9f..c161221f 100644 --- a/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java +++ b/src/main/java/analyzer/exercises/leap/LeapAnalyzer.java @@ -1,6 +1,6 @@ package analyzer.exercises.leap; -import analyzer.Analysis; +import analyzer.OutputCollector; import analyzer.Analyzer; import analyzer.Solution; import analyzer.comments.AvoidHardCodedTestCases; @@ -22,7 +22,7 @@ * * @see The leap exercise on the Java track */ -public class LeapAnalyzer extends VoidVisitorAdapter implements Analyzer { +public class LeapAnalyzer extends VoidVisitorAdapter implements Analyzer { private static final Set TEST_CASES = Set.of(1960, 1996, 2000, 2400); private static final Set DISALLOWED_IMPORTS = Set.of( "java.time", @@ -32,60 +32,60 @@ public class LeapAnalyzer extends VoidVisitorAdapter implements Analyz private final Set intLiterals = new HashSet<>(); @Override - public void analyze(Solution solution, Analysis analysis) { + public void analyze(Solution solution, OutputCollector output) { for (CompilationUnit compilationUnit : solution.getCompilationUnits()) { - compilationUnit.accept(this, analysis); + compilationUnit.accept(this, output); } } @Override - public void visit(CompilationUnit node, Analysis analysis) { + public void visit(CompilationUnit node, OutputCollector output) { // Reset state for each compilation unit this.intLiterals.clear(); - super.visit(node, analysis); + super.visit(node, output); } @Override - public void visit(ImportDeclaration node, Analysis analysis) { + public void visit(ImportDeclaration node, OutputCollector output) { if (isUsingBuiltInMethods(node)) { - analysis.addComment(new NoBuiltInMethods()); + output.addComment(new NoBuiltInMethods()); } - super.visit(node, analysis); + super.visit(node, output); } @Override - public void visit(IntegerLiteralExpr node, Analysis analysis) { + public void visit(IntegerLiteralExpr node, OutputCollector output) { if (node.asNumber() instanceof Integer i) { this.intLiterals.add(i); } if (this.intLiterals.containsAll(TEST_CASES)) { - analysis.addComment(new AvoidHardCodedTestCases()); + output.addComment(new AvoidHardCodedTestCases()); } - super.visit(node, analysis); + super.visit(node, output); } @Override - public void visit(IfStmt node, Analysis analysis) { - analysis.addComment(new AvoidConditionalLogic()); - super.visit(node, analysis); + public void visit(IfStmt node, OutputCollector output) { + output.addComment(new AvoidConditionalLogic()); + super.visit(node, output); } @Override - public void visit(ConditionalExpr node, Analysis analysis) { - analysis.addComment(new AvoidConditionalLogic()); - super.visit(node, analysis); + public void visit(ConditionalExpr node, OutputCollector output) { + output.addComment(new AvoidConditionalLogic()); + super.visit(node, output); } @Override - public void visit(MethodDeclaration node, Analysis analysis) { + public void visit(MethodDeclaration node, OutputCollector output) { if (node.getNameAsString().equals("isLeapYear") && hasMoreThanThreeChecks(node)) { - analysis.addComment(new UseMinimumNumberOfChecks()); + output.addComment(new UseMinimumNumberOfChecks()); } - super.visit(node, analysis); + super.visit(node, output); } private static boolean isUsingBuiltInMethods(ImportDeclaration node) { diff --git a/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java b/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java index 9f25e9d4..2b8b7e96 100644 --- a/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java +++ b/src/main/java/analyzer/exercises/twofer/TwoferAnalyzer.java @@ -1,6 +1,6 @@ package analyzer.exercises.twofer; -import analyzer.Analysis; +import analyzer.OutputCollector; import analyzer.Analyzer; import analyzer.Solution; import analyzer.comments.AvoidHardCodedTestCases; @@ -13,20 +13,20 @@ public class TwoferAnalyzer implements Analyzer { @Override - public void analyze(Solution solution, Analysis analysis) { + public void analyze(Solution solution, OutputCollector output) { TwoferWalker walker = new TwoferWalker(); solution.getCompilationUnits().forEach(cu -> cu.walk(walker)); if (walker.hasHardCodedTestCases) { - analysis.addComment(new AvoidHardCodedTestCases()); + output.addComment(new AvoidHardCodedTestCases()); } else if (walker.usesFormat) { - analysis.addComment(new AvoidStringFormat()); + output.addComment(new AvoidStringFormat()); } else if (walker.returnCount > 1) { - analysis.addComment(new UseOneReturn()); + output.addComment(new UseOneReturn()); } else { if (walker.usesIfStatement) { - analysis.addComment(new UseTernaryOperator()); + output.addComment(new UseTernaryOperator()); } } } diff --git a/src/test/java/analyzer/AnalyzerIntegrationTest.java b/src/test/java/analyzer/AnalyzerIntegrationTest.java new file mode 100644 index 00000000..321c6a8a --- /dev/null +++ b/src/test/java/analyzer/AnalyzerIntegrationTest.java @@ -0,0 +1,100 @@ +package analyzer; + +import analyzer.test.SolutionFromFiles; +import org.approvaltests.Approvals; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.nio.file.Path; + +import static analyzer.OutputSerializer.serialize; + +class AnalyzerIntegrationTest { + private static final Path SCENARIOS = Path.of("src/test/resources/scenarios"); + + @ParameterizedTest + @ValueSource(strings = { + "AnySolutionWithMainMethod", + "AnySolutionWithPrintStatements", + }) + void global(String scenario) throws IOException { + var path = Path.of("global", scenario + ".java"); + var solution = new SolutionFromFiles("unknown", SCENARIOS.resolve(path)); + var output = AnalyzerRoot.analyze(solution); + + Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario)); + } + + @ParameterizedTest + @ValueSource(strings = { + "ConstructorTooLong", + "MethodTooLong", + "MustUseCharAtOrCodePointAt", + "NestedCalculation", + "NestedValidation", + "OptimalWithCalculationDelegatedFromConstructor", + "OptimalWithCalculationDelegatedFromGetHammingDistance", + "OptimalWithCalculationInGetHammingDistance", + "OptimalWithValidationMethod", + "UsesCharacterLiterals", + "UsesStreamReduce", + }) + void hamming(String scenario) throws IOException { + var path = Path.of("hamming", scenario + ".java"); + var solution = new SolutionFromFiles("hamming", SCENARIOS.resolve(path)); + var output = AnalyzerRoot.analyze(solution); + + Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario)); + } + + @ParameterizedTest + @ValueSource(strings = { + "ExemplarSolution", + "ExemplarSolutionWithTodoComments", + "NoReuseOfBothMethods", + "NoReuseOfExpectedMinutesInOven", + "NoReuseOfPreparationTimeInMinutes", + }) + void lasagna(String scenario) throws IOException { + var path = Path.of("lasagna", scenario + ".java"); + var solution = new SolutionFromFiles("lasagna", SCENARIOS.resolve(path)); + var output = AnalyzerRoot.analyze(solution); + + Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario)); + } + + @ParameterizedTest + @ValueSource(strings = { + "HardCodedTestCases", + "OptimalSolution", + "UsingGregorianCalendar", + "UsingIfStatements", + "UsingJavaTime", + "UsingTernary", + "UsingTooManyChecks", + }) + void leap(String scenario) throws IOException { + var path = Path.of("leap", scenario + ".java"); + var solution = new SolutionFromFiles("leap", SCENARIOS.resolve(path)); + var output = AnalyzerRoot.analyze(solution); + + Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario)); + } + + @ParameterizedTest + @ValueSource(strings = { + "HardCodedTestCases", + "Optimal", + "OptimalNoTernary", + "UsesMultipleReturns", + "UsesStringFormat", + }) + public void twofer(String scenario) throws IOException { + var path = Path.of("twofer", scenario + ".java"); + var solution = new SolutionFromFiles("two-fer", SCENARIOS.resolve(path)); + var output = AnalyzerRoot.analyze(solution); + + Approvals.verify(serialize(output.analysis()), Approvals.NAMES.withParameters(scenario)); + } +} diff --git a/src/test/java/analyzer/CommentTest.java b/src/test/java/analyzer/CommentTest.java new file mode 100644 index 00000000..43cdda52 --- /dev/null +++ b/src/test/java/analyzer/CommentTest.java @@ -0,0 +1,104 @@ +package analyzer; + +import analyzer.test.FakeComment; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +class CommentTest { + @Nested + class Equality { + @Test + @DisplayName("Comments with same key, no parameters and no type are equal") + void sameKeyNoParamsOrType() { + var comment1 = new FakeComment().withKey("key"); + var comment2 = new FakeComment().withKey("key"); + + assertThat(comment1).isEqualTo(comment2); + assertThat(comment2).isEqualTo(comment1); + } + + @Test + @DisplayName("Comments with same key and parameters and no type are equal") + void sameKeyAndParamsNoType() { + var comment1 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")); + var comment2 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")); + + assertThat(comment1).isEqualTo(comment2); + assertThat(comment2).isEqualTo(comment1); + } + + @Test + @DisplayName("Comments with same key and type and no parameters are equal") + void sameKeyAndTypeNoParams() { + var comment1 = new FakeComment().withKey("key").withType(Comment.Type.CELEBRATORY); + var comment2 = new FakeComment().withKey("key").withType(Comment.Type.CELEBRATORY); + + assertThat(comment1).isEqualTo(comment2); + assertThat(comment2).isEqualTo(comment1); + } + + @Test + @DisplayName("Comments with same key, parameters and type are equal") + void sameKeyParamsAndType() { + var comment1 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")).withType(Comment.Type.INFORMATIVE); + var comment2 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")).withType(Comment.Type.INFORMATIVE); + + assertThat(comment1).isEqualTo(comment2); + assertThat(comment2).isEqualTo(comment1); + } + + @Test + @DisplayName("Comments with different key are not equal") + void differentKey() { + var comment1 = new FakeComment().withKey("key1"); + var comment2 = new FakeComment().withKey("key2"); + + assertThat(comment1).isNotEqualTo(comment2); + assertThat(comment2).isNotEqualTo(comment1); + } + + @Test + @DisplayName("Comments with same key but different parameters are not equal") + void sameKeyDifferentParams() { + var comment1 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")); + var comment2 = new FakeComment().withKey("key").withParameters(Map.of("param1", "b")); + + assertThat(comment1).isNotEqualTo(comment2); + assertThat(comment2).isNotEqualTo(comment1); + } + + @Test + @DisplayName("Comments with same key but subset of parameters are not equal") + void sameKeySubsetOfParams() { + var comment1 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a", "param2", "b")); + var comment2 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")); + + assertThat(comment1).isNotEqualTo(comment2); + } + + @Test + @DisplayName("Comments with same key but superset of parameters are not equal") + void sameKeySupersetOfParams() { + var comment1 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a")); + var comment2 = new FakeComment().withKey("key").withParameters(Map.of("param1", "a", "param2", "b")); + + assertThat(comment1).isNotEqualTo(comment2); + } + + @Test + @DisplayName("Comments with same key but different type are not equal") + void sameKeyDifferentType() { + var comment1 = new FakeComment().withKey("key").withType(Comment.Type.ACTIONABLE); + var comment2 = new FakeComment().withKey("key").withType(Comment.Type.INFORMATIVE); + + assertThat(comment1).isNotEqualTo(comment2); + assertThat(comment2).isNotEqualTo(comment1); + } + } + +} diff --git a/src/test/java/analyzer/OutputBuilderTest.java b/src/test/java/analyzer/OutputBuilderTest.java new file mode 100644 index 00000000..00c2649e --- /dev/null +++ b/src/test/java/analyzer/OutputBuilderTest.java @@ -0,0 +1,77 @@ +package analyzer; + +import analyzer.comments.ExemplarSolution; +import analyzer.test.FakeComment; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class OutputBuilderTest { + private OutputBuilder builder; + + @BeforeEach + void setup() { + builder = new OutputBuilder(); + } + + @Test + @DisplayName("Unable to add duplicate comments") + void testDuplicateComments() { + var comment = new ExemplarSolution("hello-world"); + builder.addComment(comment); + builder.addComment(comment); + var output = builder.build(); + + assertThat(output.analysis().comments()).containsExactly(comment); + } + + @Test + @DisplayName("Unable to add duplicate tags") + void testDuplicateTags() { + var tag = "concept:inheritance"; + builder.addTag(tag); + builder.addTag(tag); + var output = builder.build(); + + assertThat(output.tags().tags()).containsExactly(tag); + } + + @Test + @DisplayName("Summary can be overwritten") + void testOverwriteSummary() { + builder.setSummary("Initial summary"); + builder.setSummary("Second summary"); + var output = builder.build(); + + assertThat(output.analysis().summary()).isEqualTo("Second summary"); + } + + @Test + @DisplayName("Summary can be cleared") + void testClearSummary() { + builder.setSummary("Initial summary"); + builder.setSummary(null); + var output = builder.build(); + + assertThat(output.analysis().summary()).isNull(); + } + + @Test + @DisplayName("Analysis comments are ordered by type") + void testCommentOrder() { + var essential = new FakeComment().withType(Comment.Type.ESSENTIAL); + var actionable = new FakeComment().withType(Comment.Type.ACTIONABLE); + var informative = new FakeComment().withType(Comment.Type.INFORMATIVE); + var celebratory = new FakeComment().withType(Comment.Type.CELEBRATORY); + + builder.addComment(celebratory); + builder.addComment(actionable); + builder.addComment(informative); + builder.addComment(essential); + var output = builder.build(); + + assertThat(output.analysis().comments()).containsExactly(essential, actionable, informative, celebratory); + } +} diff --git a/src/test/java/analyzer/OutputWriterTest.java b/src/test/java/analyzer/OutputWriterTest.java index 4f9ce049..1e824b28 100644 --- a/src/test/java/analyzer/OutputWriterTest.java +++ b/src/test/java/analyzer/OutputWriterTest.java @@ -1,142 +1,61 @@ package analyzer; +import analyzer.test.FakeComment; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; import java.io.IOException; -import java.io.StringWriter; -import java.util.Map; -import java.util.Objects; +import java.nio.file.Path; +import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Named.named; -public class OutputWriterTest { - - private StringWriter analysisOutput; - private StringWriter tagsOutput; +class OutputWriterTest { + @TempDir + private Path outputPath; private OutputWriter outputWriter; @BeforeEach public void setup() { - analysisOutput = new StringWriter(); - tagsOutput = new StringWriter(); - outputWriter = new OutputWriter(analysisOutput, tagsOutput); + outputWriter = new OutputWriter(outputPath); } - @Test - public void serializeAnalysis() throws IOException { - var analysis = new Analysis(); - analysis.addComment(new TestComment("key_only")); - analysis.addComment(new TestComment("key_and_single_param", Map.of("param1", "value1"))); - analysis.addComment(new TestComment("key_and_multiple_params", Map.of("param1", "value1", "param2", "value2"))); - analysis.addComment(new TestComment("celebratory", Comment.Type.CELEBRATORY)); - analysis.addComment(new TestComment("actionable", Comment.Type.ACTIONABLE)); - analysis.addComment(new TestComment("essential", Comment.Type.ESSENTIAL)); - analysis.addComment(new TestComment("informative", Comment.Type.INFORMATIVE)); - analysis.setSummary("Lorum Ipsum"); - outputWriter.write(analysis); - - var expected = """ - { - "summary": "Lorum Ipsum", - "comments": [ - { - "comment": "essential", - "type": "essential" - }, - { - "comment": "actionable", - "type": "actionable" - }, - { - "comment": "informative", - "type": "informative" - }, - { - "comment": "celebratory", - "type": "celebratory" - }, - {"comment": "key_only"}, - { - "comment": "key_and_single_param", - "params": {"param1": "value1"} - }, - { - "comment": "key_and_multiple_params", - "params": { - "param1": "value1", - "param2": "value2" - } - } - ] - } - """.trim(); + private static Stream testCases() { + var empty = new OutputBuilder(); - assertThat(analysisOutput.toString()).isEqualTo(expected); - } + var onlyAnalysis = new OutputBuilder(); + onlyAnalysis.addComment(new FakeComment()); - @Test - public void serializeTags() throws IOException { - var analysis = new Analysis(); - analysis.addTag("tag1"); - analysis.addTag("tag3"); - analysis.addTag("tag2"); - outputWriter.write(analysis); + var onlyTags = new OutputBuilder(); + onlyTags.addTag("tag"); - var expected = """ - {"tags": [ - "tag1", - "tag3", - "tag2" - ]} - """.trim(); + var analysisAndTags = new OutputBuilder(); + analysisAndTags.addComment(new FakeComment()); + analysisAndTags.addTag("tag"); - assertThat(tagsOutput.toString()).isEqualTo(expected); + return Stream.of( + Arguments.of(named("Empty output", empty.build())), + Arguments.of(named("Only analysis", onlyAnalysis.build())), + Arguments.of(named("Only tags", onlyTags.build())), + Arguments.of(named("Analysis and tags", analysisAndTags.build())) + ); } - @Test - public void serializeEmptyAnalysis() throws IOException { - outputWriter.write(new Analysis()); - assertThat(analysisOutput.toString()).isEqualTo("{}"); - assertThat(tagsOutput.toString()).isEqualTo("{}"); + @ParameterizedTest + @MethodSource("testCases") + void writesAnalysisJsonFile(Output output) throws IOException { + outputWriter.write(output); + assertThat(outputPath.resolve("analysis.json").toFile().exists()).isTrue(); } - private static class TestComment extends Comment { - private final String key; - private final Type type; - private final Map parameters; - - private TestComment(String key, Type type, Map parameters) { - this.key = Objects.requireNonNull(key); - this.type = type; - this.parameters = Objects.requireNonNull(parameters); - } - - private TestComment(String key) { - this(key, null, Map.of()); - } - - private TestComment(String key, Type type) { - this(key, type, Map.of()); - } - - private TestComment(String key, Map parameters) { - this(key, null, parameters); - } - - @Override - public String getKey() { - return this.key; - } - - @Override - public Type getType() { - return this.type; - } - - @Override - public Map getParameters() { - return this.parameters; - } + @ParameterizedTest + @MethodSource("testCases") + void writesTagsJsonFile(Output output) throws IOException { + outputWriter.write(output); + assertThat(outputPath.resolve("tags.json").toFile().exists()).isTrue(); } } diff --git a/src/test/java/analyzer/PackageSettings.java b/src/test/java/analyzer/PackageSettings.java new file mode 100644 index 00000000..9cb234c5 --- /dev/null +++ b/src/test/java/analyzer/PackageSettings.java @@ -0,0 +1,9 @@ +package analyzer; + +/** + * Settings for {@link org.approvaltests.Approvals}. + */ +@SuppressWarnings("unused") +public class PackageSettings { + public static String ApprovalBaseDirectory = "../resources"; +} diff --git a/src/test/java/analyzer/SolutionFromResourceFiles.java b/src/test/java/analyzer/SolutionFromResourceFiles.java deleted file mode 100644 index 53dc90a6..00000000 --- a/src/test/java/analyzer/SolutionFromResourceFiles.java +++ /dev/null @@ -1,37 +0,0 @@ -package analyzer; - -import com.github.javaparser.StaticJavaParser; -import com.github.javaparser.ast.CompilationUnit; - -import java.util.ArrayList; -import java.util.List; - -public class SolutionFromResourceFiles implements Solution { - private final String slug; - private final List compilationUnits; - - public SolutionFromResourceFiles(String slug, String resourceFileName, String... moreResourceFileNames) { - this.slug = slug; - this.compilationUnits = new ArrayList<>(); - - compilationUnits.add(parseResourceFile(resourceFileName)); - for (String fileName : moreResourceFileNames) { - compilationUnits.add(parseResourceFile(fileName)); - } - } - - private static CompilationUnit parseResourceFile(String fileName) { - var inputStream = SolutionFromResourceFiles.class.getResourceAsStream(fileName); - return StaticJavaParser.parse(inputStream); - } - - @Override - public String getSlug() { - return this.slug; - } - - @Override - public List getCompilationUnits() { - return List.copyOf(this.compilationUnits); - } -} diff --git a/src/test/java/analyzer/SubmittedSolutionTest.java b/src/test/java/analyzer/SubmittedSolutionTest.java new file mode 100644 index 00000000..5d0e7d2b --- /dev/null +++ b/src/test/java/analyzer/SubmittedSolutionTest.java @@ -0,0 +1,25 @@ +package analyzer; + +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +class SubmittedSolutionTest { + @Test + @DisplayName("Parses all files in the main source folder of the input directory") + void testParsesFiles() throws IOException { + var path = Path.of("src/test/resources/solutions/three-solution-files"); + var solution = new SubmittedSolution("three-solution-files", path); + + assertThat(solution.getCompilationUnits()) + .hasSize(3) + .allSatisfy(compilationUnit -> + assertThat(compilationUnit.findFirst(ClassOrInterfaceDeclaration.class).get().getNameAsString()) + .isIn("Class1", "Class2", "Class3")); + } +} diff --git a/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java b/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java index 3f0f1d69..c29580ba 100644 --- a/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java +++ b/src/test/java/analyzer/exercises/GlobalAnalyzerTest.java @@ -1,9 +1,8 @@ package analyzer.exercises; -import analyzer.AnalyzerRoot; -import analyzer.SolutionFromString; import analyzer.comments.AvoidPrintStatements; import analyzer.comments.DoNotUseMainMethod; +import analyzer.test.AnalyzerUnitTestBase; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -11,14 +10,16 @@ import static org.assertj.core.api.Assertions.assertThat; -public class GlobalAnalyzerTest { +class GlobalAnalyzerTest extends AnalyzerUnitTestBase { + GlobalAnalyzerTest() { + super(GlobalAnalyzer::new); + } @MethodSource @ParameterizedTest public void solutionsWithMainMethod(String code) { - var solution = new SolutionFromString("any-exercise", code); - var actual = AnalyzerRoot.analyze(solution); - assertThat(actual.getComments()).contains(new DoNotUseMainMethod()); + var output = analyze(code); + assertThat(output.getComments()).contains(new DoNotUseMainMethod()); } private static Stream solutionsWithMainMethod() { @@ -41,9 +42,8 @@ public static void main(String[] args) {} @MethodSource @ParameterizedTest public void solutionsWithPrintStatements(String code) { - var solution = new SolutionFromString("any-exercise", code); - var actual = AnalyzerRoot.analyze(solution); - assertThat(actual.getComments()).contains(new AvoidPrintStatements()); + var output = analyze(code); + assertThat(output.getComments()).contains(new AvoidPrintStatements()); } private static Stream solutionsWithPrintStatements() { diff --git a/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java b/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java deleted file mode 100644 index 357238f0..00000000 --- a/src/test/java/analyzer/exercises/hamming/HammingAnalyzerTest.java +++ /dev/null @@ -1,45 +0,0 @@ -package analyzer.exercises.hamming; - -import analyzer.AnalyzerRoot; -import analyzer.Comment; -import analyzer.SolutionFromResourceFiles; -import analyzer.comments.ConstructorTooLong; -import analyzer.comments.MethodTooLong; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -public class HammingAnalyzerTest { - - private static Stream testCases() { - return Stream.of( - Arguments.of("UsesCharacterLiterals.java.txt", new Comment[]{new AvoidCharacterLiterals()}), - Arguments.of("MustUseCharAtOrCodePointAt.java.txt", new Comment[]{new MustUseStringCharAtOrCodePointAt()}), - Arguments.of("NestedValidation.java.txt", new Comment[]{new CalculateDistanceInConstructor()}), - Arguments.of("NestedCalculation.java.txt", new Comment[0]), - Arguments.of("OptimalWithCalculationInGetHammingDistance.java.txt", new Comment[]{new CalculateDistanceInConstructor()}), - Arguments.of("OptimalWithCalculationDelegatedFromGetHammingDistance.java.txt", new Comment[]{new CalculateDistanceInConstructor()}), - Arguments.of("ConstructorTooLong.java.txt", new Comment[]{new ConstructorTooLong("Hamming")}), - Arguments.of("MethodTooLong.java.txt", new Comment[]{new MethodTooLong("calculateHammingDistance")}), - Arguments.of("UsesStreamReduce.java.txt", new Comment[]{new ShouldUseStreamFilterAndCount()}), - Arguments.of("OptimalWithCalculationDelegatedFromConstructor.java.txt", new Comment[0]), - Arguments.of("OptimalWithValidationMethod.java.txt", new Comment[0])); - } - - @MethodSource("testCases") - @ParameterizedTest(name = "{0}") - public void testCommentsOnSolution(String solutionFile, Comment... expectedComments) { - var solution = new SolutionFromResourceFiles("hamming", getResourceFileName(solutionFile)); - var analysis = AnalyzerRoot.analyze(solution); - - assertThat(analysis.getComments()).contains(expectedComments); - } - - private static String getResourceFileName(String testFileName) { - return "/analyzer/exercises/hamming/" + testFileName; - } -} diff --git a/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java b/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java deleted file mode 100644 index 09685efb..00000000 --- a/src/test/java/analyzer/exercises/lasagna/LasagnaAnalyzerTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package analyzer.exercises.lasagna; - -import analyzer.AnalyzerRoot; -import analyzer.Comment; -import analyzer.SolutionFromResourceFiles; -import analyzer.comments.ExemplarSolution; -import analyzer.comments.RemoveTodoComments; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.List; -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -public class LasagnaAnalyzerTest { - - private static Stream testCases() { - return Stream.of( - Arguments.of("ExemplarSolution.java", List.of(new ExemplarSolution("Lasagna"))), - Arguments.of("ExemplarSolutionWithTodoComments.java", List.of(new RemoveTodoComments())), - Arguments.of("NoReuseOfExpectedMinutesInOven.java", - List.of(new ReuseCode("remainingMinutesInOven", "expectedMinutesInOven"))), - Arguments.of("NoReuseOfPreparationTimeInMinutes.java", - List.of(new ReuseCode("totalTimeInMinutes", "preparationTimeInMinutes"))), - Arguments.of("NoReuseOfBothMethods.java", - List.of( - new ReuseCode("remainingMinutesInOven", "expectedMinutesInOven"), - new ReuseCode("totalTimeInMinutes", "preparationTimeInMinutes") - ) - ) - ); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("testCases") - public void testCommentsOnSolution(String filename, List expectedComments) { - var solution = new SolutionFromResourceFiles("lasagna", "/analyzer/exercises/lasagna/" + filename); - var analysis = AnalyzerRoot.analyze(solution); - assertThat(analysis.getComments()).contains(expectedComments.toArray(Comment[]::new)); - } -} diff --git a/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java b/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java deleted file mode 100644 index b25412c0..00000000 --- a/src/test/java/analyzer/exercises/leap/LeapAnalyzerTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package analyzer.exercises.leap; - -import analyzer.AnalyzerRoot; -import analyzer.Comment; -import analyzer.SolutionFromResourceFiles; -import analyzer.comments.AvoidHardCodedTestCases; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -public class LeapAnalyzerTest { - - @Test - public void optimalSolution() { - var solution = new SolutionFromResourceFiles("leap", getResourceFileName("OptimalSolution.java")); - var analysis = AnalyzerRoot.analyze(solution); - assertThat(analysis.getComments()).isEmpty(); - } - - private static Stream testCases() { - return Stream.of( - Arguments.of("HardCodedTestCases.java", new AvoidHardCodedTestCases()), - Arguments.of("UsingGregorianCalendar.java", new NoBuiltInMethods()), - Arguments.of("UsingIfStatements.java", new AvoidConditionalLogic()), - Arguments.of("UsingJavaTime.java", new NoBuiltInMethods()), - Arguments.of("UsingTernary.java", new AvoidConditionalLogic()), - Arguments.of("UsingTooManyChecks.java", new UseMinimumNumberOfChecks()) - ); - } - - @ParameterizedTest(name = "{0}") - @MethodSource("testCases") - public void testCommentsOnSolution(String filename, Comment expectedComment) { - var solution = new SolutionFromResourceFiles("leap", getResourceFileName(filename)); - var analysis = AnalyzerRoot.analyze(solution); - assertThat(analysis.getComments()).contains(expectedComment); - } - - private static String getResourceFileName(String testFileName) { - return "/analyzer/exercises/leap/" + testFileName; - } -} diff --git a/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java b/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java deleted file mode 100644 index 86609226..00000000 --- a/src/test/java/analyzer/exercises/twofer/TwoferAnalyzerTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package analyzer.exercises.twofer; - -import analyzer.AnalyzerRoot; -import analyzer.Comment; -import analyzer.SolutionFromResourceFiles; -import analyzer.comments.AvoidHardCodedTestCases; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import java.util.stream.Stream; - -import static org.assertj.core.api.Assertions.assertThat; - -public class TwoferAnalyzerTest { - - private static Stream testCases() { - return Stream.of( - Arguments.of("HardCodedTestCases.java.txt", new Comment[]{new AvoidHardCodedTestCases()}), - Arguments.of("UsesStringFormat.java.txt", new Comment[]{new AvoidStringFormat()}), - Arguments.of("UsesMultipleReturns.java.txt", new Comment[]{new UseOneReturn()}), - Arguments.of("OptimalNoTernary.java.txt", new Comment[]{new UseTernaryOperator()}), - Arguments.of("Optimal.java.txt", new Comment[0]) - ); - } - - @MethodSource("testCases") - @ParameterizedTest(name = "{0}") - public void testCommentsOnSolution(String solutionFile, Comment... expectedComments) { - var solution = new SolutionFromResourceFiles("two-fer", getResourceFileName(solutionFile)); - var actual = AnalyzerRoot.analyze(solution); - - assertThat(actual.getComments()).contains(expectedComments); - } - - private static String getResourceFileName(String testFileName) { - return "/analyzer/exercises/twofer/" + testFileName; - } -} diff --git a/src/test/java/analyzer/test/AnalyzerUnitTestBase.java b/src/test/java/analyzer/test/AnalyzerUnitTestBase.java new file mode 100644 index 00000000..df63ef37 --- /dev/null +++ b/src/test/java/analyzer/test/AnalyzerUnitTestBase.java @@ -0,0 +1,41 @@ +package analyzer.test; + +import analyzer.Analyzer; +import analyzer.OutputCollector; +import analyzer.Solution; +import org.junit.jupiter.api.BeforeEach; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Supplier; + +/** + * Base class for unit testing individual analyzers. + */ +public abstract class AnalyzerUnitTestBase { + private final Supplier createAnalyzer; + private Analyzer analyzer; + + protected AnalyzerUnitTestBase(Supplier createAnalyzer) { + this.createAnalyzer = createAnalyzer; + } + + @BeforeEach + protected void setup() { + analyzer = createAnalyzer.get(); + } + + protected OutputCollector analyze(String code) { + return analyze(new SolutionFromString("", code)); + } + + protected OutputCollector analyze(Path... files) throws IOException { + return analyze(new SolutionFromFiles("", files)); + } + + private OutputCollector analyze(Solution solution) { + var collector = new FakeOutputCollector(); + this.analyzer.analyze(solution, collector); + return collector; + } +} diff --git a/src/test/java/analyzer/test/FakeComment.java b/src/test/java/analyzer/test/FakeComment.java new file mode 100644 index 00000000..04ced04c --- /dev/null +++ b/src/test/java/analyzer/test/FakeComment.java @@ -0,0 +1,52 @@ +package analyzer.test; + +import analyzer.Comment; + +import java.util.Map; +import java.util.Optional; + +public class FakeComment extends Comment { + private String key; + private Map parameters; + private Type type; + + public FakeComment() { + this(null, null, null); + } + + public FakeComment(String key, Map parameters, Type type) { + this.key = key; + this.parameters = parameters; + this.type = type; + } + + @Override + public String getKey() { + return Optional.ofNullable(this.key).orElse("java.test.fake"); + } + + public FakeComment withKey(String key) { + this.key = key; + return this; + } + + @Override + public Map getParameters() { + return Optional.ofNullable(this.parameters).orElse(super.getParameters()); + } + + public FakeComment withParameters(Map parameters) { + this.parameters = parameters; + return this; + } + + @Override + public Type getType() { + return Optional.ofNullable(this.type).orElse(super.getType()); + } + + public FakeComment withType(Type type) { + this.type = type; + return this; + } +} diff --git a/src/test/java/analyzer/test/FakeOutputCollector.java b/src/test/java/analyzer/test/FakeOutputCollector.java new file mode 100644 index 00000000..d2eba27b --- /dev/null +++ b/src/test/java/analyzer/test/FakeOutputCollector.java @@ -0,0 +1,43 @@ +package analyzer.test; + +import analyzer.Comment; +import analyzer.OutputCollector; + +import java.util.ArrayList; +import java.util.List; + +public class FakeOutputCollector implements OutputCollector { + private final List comments = new ArrayList<>(); + private final List tags = new ArrayList<>(); + private String summary; + + @Override + public String getSummary() { + return this.summary; + } + + @Override + public void setSummary(String summary) { + this.summary = summary; + } + + @Override + public List getComments() { + return List.copyOf(this.comments); + } + + @Override + public List getTags() { + return List.copyOf(this.tags); + } + + @Override + public void addComment(Comment comment) { + this.comments.add(comment); + } + + @Override + public void addTag(String tag) { + this.tags.add(tag); + } +} diff --git a/src/test/java/analyzer/test/SolutionFromFiles.java b/src/test/java/analyzer/test/SolutionFromFiles.java new file mode 100644 index 00000000..f49f26a8 --- /dev/null +++ b/src/test/java/analyzer/test/SolutionFromFiles.java @@ -0,0 +1,37 @@ +package analyzer.test; + +import analyzer.Solution; +import com.github.javaparser.StaticJavaParser; +import com.github.javaparser.ast.CompilationUnit; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * Helper to create a solution from one or more files. + */ +public final class SolutionFromFiles implements Solution { + private final String slug; + private final List compilationUnits; + + public SolutionFromFiles(String slug, Path... files) throws IOException { + this.slug = slug; + this.compilationUnits = new ArrayList<>(); + + for (var file : files) { + compilationUnits.add(StaticJavaParser.parse(file)); + } + } + + @Override + public String getSlug() { + return this.slug; + } + + @Override + public List getCompilationUnits() { + return List.copyOf(this.compilationUnits); + } +} diff --git a/src/test/java/analyzer/SolutionFromString.java b/src/test/java/analyzer/test/SolutionFromString.java similarity index 75% rename from src/test/java/analyzer/SolutionFromString.java rename to src/test/java/analyzer/test/SolutionFromString.java index d2df0dd4..b7746933 100644 --- a/src/test/java/analyzer/SolutionFromString.java +++ b/src/test/java/analyzer/test/SolutionFromString.java @@ -1,11 +1,15 @@ -package analyzer; +package analyzer.test; +import analyzer.Solution; import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; import java.util.List; -public class SolutionFromString implements Solution { +/** + * Helper to create a solution from a string containing Java code. + */ +public final class SolutionFromString implements Solution { private final String slug; private final CompilationUnit compilationUnit; diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.global.AnySolutionWithMainMethod.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.global.AnySolutionWithMainMethod.approved.txt new file mode 100644 index 00000000..405a3dae --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.global.AnySolutionWithMainMethod.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.general.do_not_use_main_method", + "params": {}, + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.global.AnySolutionWithPrintStatements.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.global.AnySolutionWithPrintStatements.approved.txt new file mode 100644 index 00000000..2ca55778 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.global.AnySolutionWithPrintStatements.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.general.avoid_print_statements", + "params": {}, + "type": "informative" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.ConstructorTooLong.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.ConstructorTooLong.approved.txt new file mode 100644 index 00000000..3581d54d --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.ConstructorTooLong.approved.txt @@ -0,0 +1,16 @@ +{ + "comments": [ + { + "comment": "java.general.constructor_too_long", + "params": { + "constructorNames": "Hamming" + }, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.MethodTooLong.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.MethodTooLong.approved.txt new file mode 100644 index 00000000..c492439a --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.MethodTooLong.approved.txt @@ -0,0 +1,16 @@ +{ + "comments": [ + { + "comment": "java.general.method_too_long", + "params": { + "methodNames": "calculateHammingDistance" + }, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.MustUseCharAtOrCodePointAt.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.MustUseCharAtOrCodePointAt.approved.txt new file mode 100644 index 00000000..6439b67c --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.MustUseCharAtOrCodePointAt.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.must_use_string_char_at_or_code_point_at", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.NestedCalculation.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.NestedCalculation.approved.txt new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.NestedCalculation.approved.txt @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.NestedValidation.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.NestedValidation.approved.txt new file mode 100644 index 00000000..8983e22f --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.NestedValidation.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.calculate_distance_in_constructor", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationDelegatedFromConstructor.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationDelegatedFromConstructor.approved.txt new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationDelegatedFromConstructor.approved.txt @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationDelegatedFromGetHammingDistance.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationDelegatedFromGetHammingDistance.approved.txt new file mode 100644 index 00000000..8983e22f --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationDelegatedFromGetHammingDistance.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.calculate_distance_in_constructor", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationInGetHammingDistance.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationInGetHammingDistance.approved.txt new file mode 100644 index 00000000..8983e22f --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithCalculationInGetHammingDistance.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.calculate_distance_in_constructor", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithValidationMethod.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithValidationMethod.approved.txt new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.OptimalWithValidationMethod.approved.txt @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.UsesCharacterLiterals.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.UsesCharacterLiterals.approved.txt new file mode 100644 index 00000000..4e556ad0 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.UsesCharacterLiterals.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.avoid_character_literals", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.UsesStreamReduce.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.UsesStreamReduce.approved.txt new file mode 100644 index 00000000..651078f0 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.hamming.UsesStreamReduce.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.should_use_stream_filter_and_count", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.ExemplarSolution.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.ExemplarSolution.approved.txt new file mode 100644 index 00000000..63e7c9a4 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.ExemplarSolution.approved.txt @@ -0,0 +1,11 @@ +{ + "comments": [ + { + "comment": "java.general.exemplar", + "params": { + "exerciseName": "Lasagna" + }, + "type": "celebratory" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.ExemplarSolutionWithTodoComments.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.ExemplarSolutionWithTodoComments.approved.txt new file mode 100644 index 00000000..58bf320f --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.ExemplarSolutionWithTodoComments.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.general.remove_todo_comments", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfBothMethods.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfBothMethods.approved.txt new file mode 100644 index 00000000..f9f74609 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfBothMethods.approved.txt @@ -0,0 +1,25 @@ +{ + "comments": [ + { + "comment": "java.lasagna.reuse_code", + "params": { + "callingMethod": "remainingMinutesInOven", + "methodToCall": "expectedMinutesInOven" + }, + "type": "actionable" + }, + { + "comment": "java.lasagna.reuse_code", + "params": { + "callingMethod": "totalTimeInMinutes", + "methodToCall": "preparationTimeInMinutes" + }, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfExpectedMinutesInOven.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfExpectedMinutesInOven.approved.txt new file mode 100644 index 00000000..d5dcc6e9 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfExpectedMinutesInOven.approved.txt @@ -0,0 +1,17 @@ +{ + "comments": [ + { + "comment": "java.lasagna.reuse_code", + "params": { + "callingMethod": "remainingMinutesInOven", + "methodToCall": "expectedMinutesInOven" + }, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfPreparationTimeInMinutes.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfPreparationTimeInMinutes.approved.txt new file mode 100644 index 00000000..3ad37e36 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.lasagna.NoReuseOfPreparationTimeInMinutes.approved.txt @@ -0,0 +1,17 @@ +{ + "comments": [ + { + "comment": "java.lasagna.reuse_code", + "params": { + "callingMethod": "totalTimeInMinutes", + "methodToCall": "preparationTimeInMinutes" + }, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.HardCodedTestCases.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.HardCodedTestCases.approved.txt new file mode 100644 index 00000000..ee54550b --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.HardCodedTestCases.approved.txt @@ -0,0 +1,19 @@ +{ + "comments": [ + { + "comment": "java.general.avoid_hard_coded_test_cases", + "params": {}, + "type": "essential" + }, + { + "comment": "java.leap.use_minimum_number_of_checks", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.OptimalSolution.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.OptimalSolution.approved.txt new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.OptimalSolution.approved.txt @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingGregorianCalendar.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingGregorianCalendar.approved.txt new file mode 100644 index 00000000..a5ce5c42 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingGregorianCalendar.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.leap.no_built_in_methods", + "params": {}, + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingIfStatements.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingIfStatements.approved.txt new file mode 100644 index 00000000..675b1c3a --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingIfStatements.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.leap.avoid_conditional_logic", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingJavaTime.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingJavaTime.approved.txt new file mode 100644 index 00000000..a5ce5c42 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingJavaTime.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.leap.no_built_in_methods", + "params": {}, + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingTernary.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingTernary.approved.txt new file mode 100644 index 00000000..675b1c3a --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingTernary.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.leap.avoid_conditional_logic", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingTooManyChecks.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingTooManyChecks.approved.txt new file mode 100644 index 00000000..ce6daeef --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.leap.UsingTooManyChecks.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.leap.use_minimum_number_of_checks", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.HardCodedTestCases.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.HardCodedTestCases.approved.txt new file mode 100644 index 00000000..e5fab09b --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.HardCodedTestCases.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.general.avoid_hard_coded_test_cases", + "params": {}, + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.Optimal.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.Optimal.approved.txt new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.Optimal.approved.txt @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.OptimalNoTernary.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.OptimalNoTernary.approved.txt new file mode 100644 index 00000000..a0a565af --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.OptimalNoTernary.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.two-fer.use_ternary_operator", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.UsesMultipleReturns.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.UsesMultipleReturns.approved.txt new file mode 100644 index 00000000..e7bd4728 --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.UsesMultipleReturns.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.two-fer.use_one_return", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.UsesStringFormat.approved.txt b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.UsesStringFormat.approved.txt new file mode 100644 index 00000000..184c68eb --- /dev/null +++ b/src/test/resources/analyzer/AnalyzerIntegrationTest.twofer.UsesStringFormat.approved.txt @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.two-fer.avoid_string_format", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/src/test/resources/scenarios/global/AnySolutionWithMainMethod.java b/src/test/resources/scenarios/global/AnySolutionWithMainMethod.java new file mode 100644 index 00000000..674ac61d --- /dev/null +++ b/src/test/resources/scenarios/global/AnySolutionWithMainMethod.java @@ -0,0 +1,5 @@ +class AnySolution { + static void main(String[] args) { + + } +} diff --git a/src/test/resources/scenarios/global/AnySolutionWithPrintStatements.java b/src/test/resources/scenarios/global/AnySolutionWithPrintStatements.java new file mode 100644 index 00000000..ccdf13cd --- /dev/null +++ b/src/test/resources/scenarios/global/AnySolutionWithPrintStatements.java @@ -0,0 +1,6 @@ +class AnySolution { + int answer() { + System.out.println("Hello from answer()"); + return 42; + } +} diff --git a/src/test/resources/analyzer/exercises/hamming/ConstructorTooLong.java.txt b/src/test/resources/scenarios/hamming/ConstructorTooLong.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/ConstructorTooLong.java.txt rename to src/test/resources/scenarios/hamming/ConstructorTooLong.java diff --git a/src/test/resources/analyzer/exercises/hamming/MethodTooLong.java.txt b/src/test/resources/scenarios/hamming/MethodTooLong.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/MethodTooLong.java.txt rename to src/test/resources/scenarios/hamming/MethodTooLong.java diff --git a/src/test/resources/analyzer/exercises/hamming/MustUseCharAtOrCodePointAt.java.txt b/src/test/resources/scenarios/hamming/MustUseCharAtOrCodePointAt.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/MustUseCharAtOrCodePointAt.java.txt rename to src/test/resources/scenarios/hamming/MustUseCharAtOrCodePointAt.java diff --git a/src/test/resources/analyzer/exercises/hamming/NestedCalculation.java.txt b/src/test/resources/scenarios/hamming/NestedCalculation.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/NestedCalculation.java.txt rename to src/test/resources/scenarios/hamming/NestedCalculation.java diff --git a/src/test/resources/analyzer/exercises/hamming/NestedValidation.java.txt b/src/test/resources/scenarios/hamming/NestedValidation.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/NestedValidation.java.txt rename to src/test/resources/scenarios/hamming/NestedValidation.java diff --git a/src/test/resources/analyzer/exercises/hamming/OptimalWithCalculationDelegatedFromConstructor.java.txt b/src/test/resources/scenarios/hamming/OptimalWithCalculationDelegatedFromConstructor.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/OptimalWithCalculationDelegatedFromConstructor.java.txt rename to src/test/resources/scenarios/hamming/OptimalWithCalculationDelegatedFromConstructor.java diff --git a/src/test/resources/analyzer/exercises/hamming/OptimalWithCalculationDelegatedFromGetHammingDistance.java.txt b/src/test/resources/scenarios/hamming/OptimalWithCalculationDelegatedFromGetHammingDistance.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/OptimalWithCalculationDelegatedFromGetHammingDistance.java.txt rename to src/test/resources/scenarios/hamming/OptimalWithCalculationDelegatedFromGetHammingDistance.java diff --git a/src/test/resources/analyzer/exercises/hamming/OptimalWithCalculationInGetHammingDistance.java.txt b/src/test/resources/scenarios/hamming/OptimalWithCalculationInGetHammingDistance.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/OptimalWithCalculationInGetHammingDistance.java.txt rename to src/test/resources/scenarios/hamming/OptimalWithCalculationInGetHammingDistance.java diff --git a/src/test/resources/analyzer/exercises/hamming/OptimalWithValidationMethod.java.txt b/src/test/resources/scenarios/hamming/OptimalWithValidationMethod.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/OptimalWithValidationMethod.java.txt rename to src/test/resources/scenarios/hamming/OptimalWithValidationMethod.java diff --git a/src/test/resources/analyzer/exercises/hamming/UsesCharacterLiterals.java.txt b/src/test/resources/scenarios/hamming/UsesCharacterLiterals.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/UsesCharacterLiterals.java.txt rename to src/test/resources/scenarios/hamming/UsesCharacterLiterals.java diff --git a/src/test/resources/analyzer/exercises/hamming/UsesStreamReduce.java.txt b/src/test/resources/scenarios/hamming/UsesStreamReduce.java similarity index 100% rename from src/test/resources/analyzer/exercises/hamming/UsesStreamReduce.java.txt rename to src/test/resources/scenarios/hamming/UsesStreamReduce.java diff --git a/src/test/resources/analyzer/exercises/lasagna/ExemplarSolution.java b/src/test/resources/scenarios/lasagna/ExemplarSolution.java similarity index 100% rename from src/test/resources/analyzer/exercises/lasagna/ExemplarSolution.java rename to src/test/resources/scenarios/lasagna/ExemplarSolution.java diff --git a/src/test/resources/analyzer/exercises/lasagna/ExemplarSolutionWithTodoComments.java b/src/test/resources/scenarios/lasagna/ExemplarSolutionWithTodoComments.java similarity index 100% rename from src/test/resources/analyzer/exercises/lasagna/ExemplarSolutionWithTodoComments.java rename to src/test/resources/scenarios/lasagna/ExemplarSolutionWithTodoComments.java diff --git a/src/test/resources/analyzer/exercises/lasagna/NoReuseOfBothMethods.java b/src/test/resources/scenarios/lasagna/NoReuseOfBothMethods.java similarity index 100% rename from src/test/resources/analyzer/exercises/lasagna/NoReuseOfBothMethods.java rename to src/test/resources/scenarios/lasagna/NoReuseOfBothMethods.java diff --git a/src/test/resources/analyzer/exercises/lasagna/NoReuseOfExpectedMinutesInOven.java b/src/test/resources/scenarios/lasagna/NoReuseOfExpectedMinutesInOven.java similarity index 100% rename from src/test/resources/analyzer/exercises/lasagna/NoReuseOfExpectedMinutesInOven.java rename to src/test/resources/scenarios/lasagna/NoReuseOfExpectedMinutesInOven.java diff --git a/src/test/resources/analyzer/exercises/lasagna/NoReuseOfPreparationTimeInMinutes.java b/src/test/resources/scenarios/lasagna/NoReuseOfPreparationTimeInMinutes.java similarity index 100% rename from src/test/resources/analyzer/exercises/lasagna/NoReuseOfPreparationTimeInMinutes.java rename to src/test/resources/scenarios/lasagna/NoReuseOfPreparationTimeInMinutes.java diff --git a/src/test/resources/analyzer/exercises/leap/HardCodedTestCases.java b/src/test/resources/scenarios/leap/HardCodedTestCases.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/HardCodedTestCases.java rename to src/test/resources/scenarios/leap/HardCodedTestCases.java diff --git a/src/test/resources/analyzer/exercises/leap/OptimalSolution.java b/src/test/resources/scenarios/leap/OptimalSolution.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/OptimalSolution.java rename to src/test/resources/scenarios/leap/OptimalSolution.java diff --git a/src/test/resources/analyzer/exercises/leap/UsingGregorianCalendar.java b/src/test/resources/scenarios/leap/UsingGregorianCalendar.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/UsingGregorianCalendar.java rename to src/test/resources/scenarios/leap/UsingGregorianCalendar.java diff --git a/src/test/resources/analyzer/exercises/leap/UsingIfStatements.java b/src/test/resources/scenarios/leap/UsingIfStatements.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/UsingIfStatements.java rename to src/test/resources/scenarios/leap/UsingIfStatements.java diff --git a/src/test/resources/analyzer/exercises/leap/UsingJavaTime.java b/src/test/resources/scenarios/leap/UsingJavaTime.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/UsingJavaTime.java rename to src/test/resources/scenarios/leap/UsingJavaTime.java diff --git a/src/test/resources/analyzer/exercises/leap/UsingTernary.java b/src/test/resources/scenarios/leap/UsingTernary.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/UsingTernary.java rename to src/test/resources/scenarios/leap/UsingTernary.java diff --git a/src/test/resources/analyzer/exercises/leap/UsingTooManyChecks.java b/src/test/resources/scenarios/leap/UsingTooManyChecks.java similarity index 100% rename from src/test/resources/analyzer/exercises/leap/UsingTooManyChecks.java rename to src/test/resources/scenarios/leap/UsingTooManyChecks.java diff --git a/src/test/resources/analyzer/exercises/twofer/HardCodedTestCases.java.txt b/src/test/resources/scenarios/twofer/HardCodedTestCases.java similarity index 100% rename from src/test/resources/analyzer/exercises/twofer/HardCodedTestCases.java.txt rename to src/test/resources/scenarios/twofer/HardCodedTestCases.java diff --git a/src/test/resources/analyzer/exercises/twofer/Optimal.java.txt b/src/test/resources/scenarios/twofer/Optimal.java similarity index 100% rename from src/test/resources/analyzer/exercises/twofer/Optimal.java.txt rename to src/test/resources/scenarios/twofer/Optimal.java diff --git a/src/test/resources/analyzer/exercises/twofer/OptimalNoTernary.java.txt b/src/test/resources/scenarios/twofer/OptimalNoTernary.java similarity index 100% rename from src/test/resources/analyzer/exercises/twofer/OptimalNoTernary.java.txt rename to src/test/resources/scenarios/twofer/OptimalNoTernary.java diff --git a/src/test/resources/analyzer/exercises/twofer/UsesMultipleReturns.java.txt b/src/test/resources/scenarios/twofer/UsesMultipleReturns.java similarity index 100% rename from src/test/resources/analyzer/exercises/twofer/UsesMultipleReturns.java.txt rename to src/test/resources/scenarios/twofer/UsesMultipleReturns.java diff --git a/src/test/resources/analyzer/exercises/twofer/UsesStringFormat.java.txt b/src/test/resources/scenarios/twofer/UsesStringFormat.java similarity index 100% rename from src/test/resources/analyzer/exercises/twofer/UsesStringFormat.java.txt rename to src/test/resources/scenarios/twofer/UsesStringFormat.java diff --git a/src/test/resources/solutions/three-solution-files/.meta/config.json b/src/test/resources/solutions/three-solution-files/.meta/config.json new file mode 100644 index 00000000..4dd861b4 --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/.meta/config.json @@ -0,0 +1,25 @@ +{ + "authors": [ + "sanderploegsma" + ], + "files": { + "solution": [ + "src/main/java/Class1.java", + "src/main/java/Class2.java", + "src/main/java/Class3.java" + ], + "test": [ + "src/test/java/TestClass1.java", + "src/test/java/TestClass2.java" + ], + "exemplar": [ + ".meta/src/reference/java/ReferenceClass1.java", + ".meta/src/reference/java/ReferenceClass2.java", + ".meta/src/reference/java/ReferenceClass3.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Imaginary exercise for analyzer unit tests" +} \ No newline at end of file diff --git a/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass1.java b/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass1.java new file mode 100644 index 00000000..2eef7203 --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass1.java @@ -0,0 +1,3 @@ +class ReferenceClass1 { + +} diff --git a/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass2.java b/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass2.java new file mode 100644 index 00000000..fc4b15c6 --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass2.java @@ -0,0 +1,3 @@ +class ReferenceClass2 { + +} diff --git a/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass3.java b/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass3.java new file mode 100644 index 00000000..4c4f5910 --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/.meta/src/reference/java/ReferenceClass3.java @@ -0,0 +1,3 @@ +class ReferenceClass3 { + +} diff --git a/src/test/resources/solutions/three-solution-files/src/main/java/Class1.java b/src/test/resources/solutions/three-solution-files/src/main/java/Class1.java new file mode 100644 index 00000000..75440167 --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/src/main/java/Class1.java @@ -0,0 +1,3 @@ +class Class1 { + +} diff --git a/src/test/resources/solutions/three-solution-files/src/main/java/Class2.java b/src/test/resources/solutions/three-solution-files/src/main/java/Class2.java new file mode 100644 index 00000000..0f9f2afa --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/src/main/java/Class2.java @@ -0,0 +1,3 @@ +class Class2 { + +} diff --git a/src/test/resources/solutions/three-solution-files/src/main/java/Class3.java b/src/test/resources/solutions/three-solution-files/src/main/java/Class3.java new file mode 100644 index 00000000..d9efeca9 --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/src/main/java/Class3.java @@ -0,0 +1,3 @@ +class Class3 { + +} diff --git a/src/test/resources/solutions/three-solution-files/src/test/java/TestClass1.java b/src/test/resources/solutions/three-solution-files/src/test/java/TestClass1.java new file mode 100644 index 00000000..b760355b --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/src/test/java/TestClass1.java @@ -0,0 +1,3 @@ +class TestClass1 { + +} diff --git a/src/test/resources/solutions/three-solution-files/src/test/java/TestClass2.java b/src/test/resources/solutions/three-solution-files/src/test/java/TestClass2.java new file mode 100644 index 00000000..7d80a2ed --- /dev/null +++ b/src/test/resources/solutions/three-solution-files/src/test/java/TestClass2.java @@ -0,0 +1,3 @@ +class TestClass2 { + +} diff --git a/tests/hamming/expected_analysis.json b/tests/hamming/expected_analysis.json deleted file mode 100644 index 36d33952..00000000 --- a/tests/hamming/expected_analysis.json +++ /dev/null @@ -1,10 +0,0 @@ -{"comments": [ - { - "comment": "java.hamming.calculate_distance_in_constructor", - "type": "actionable" - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file diff --git a/tests/hamming/expected_tags.json b/tests/hamming/expected_tags.json deleted file mode 100644 index 9e26dfee..00000000 --- a/tests/hamming/expected_tags.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/tests/hamming/.meta/config.json b/tests/hamming/non-optimal-solution/.meta/config.json similarity index 100% rename from tests/hamming/.meta/config.json rename to tests/hamming/non-optimal-solution/.meta/config.json diff --git a/tests/hamming/non-optimal-solution/expected_analysis.json b/tests/hamming/non-optimal-solution/expected_analysis.json new file mode 100644 index 00000000..8983e22f --- /dev/null +++ b/tests/hamming/non-optimal-solution/expected_analysis.json @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.hamming.calculate_distance_in_constructor", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/tests/hamming/non-optimal-solution/expected_tags.json b/tests/hamming/non-optimal-solution/expected_tags.json new file mode 100644 index 00000000..eb25b190 --- /dev/null +++ b/tests/hamming/non-optimal-solution/expected_tags.json @@ -0,0 +1,3 @@ +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/hamming/src/main/java/Hamming.java b/tests/hamming/non-optimal-solution/src/main/java/Hamming.java similarity index 100% rename from tests/hamming/src/main/java/Hamming.java rename to tests/hamming/non-optimal-solution/src/main/java/Hamming.java diff --git a/tests/hamming/optimal-solution/.meta/config.json b/tests/hamming/optimal-solution/.meta/config.json new file mode 100644 index 00000000..5e2dba3a --- /dev/null +++ b/tests/hamming/optimal-solution/.meta/config.json @@ -0,0 +1,49 @@ +{ + "authors": [ + "wdjunaidi" + ], + "contributors": [ + "c-thornton", + "ChristianWilkie", + "FridaTveit", + "javaeeeee", + "jmrunkle", + "jonnynabors", + "jtigger", + "kytrinyx", + "lemoncurry", + "matthewmorgan", + "michael-berger-FR", + "michaelspets", + "mirkoperillo", + "msomji", + "muzimuzhi", + "odzeno", + "sjwarner-bp", + "SleeplessByte", + "Smarticles101", + "sshine", + "stkent", + "t0dd", + "Valkryst", + "vasouv", + "Zaldrick" + ], + "files": { + "solution": [ + "src/main/java/Hamming.java" + ], + "test": [ + "src/test/java/HammingTest.java" + ], + "example": [ + ".meta/src/reference/java/Hamming.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Calculate the Hamming difference between two DNA strands.", + "source": "The Calculating Point Mutations problem at Rosalind", + "source_url": "https://rosalind.info/problems/hamm/" +} diff --git a/tests/hamming/optimal-solution/expected_analysis.json b/tests/hamming/optimal-solution/expected_analysis.json new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/tests/hamming/optimal-solution/expected_analysis.json @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/tests/hamming/optimal-solution/expected_tags.json b/tests/hamming/optimal-solution/expected_tags.json new file mode 100644 index 00000000..eb25b190 --- /dev/null +++ b/tests/hamming/optimal-solution/expected_tags.json @@ -0,0 +1,3 @@ +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/hamming/optimal-solution/src/main/java/Hamming.java b/tests/hamming/optimal-solution/src/main/java/Hamming.java new file mode 100644 index 00000000..1d90a5f9 --- /dev/null +++ b/tests/hamming/optimal-solution/src/main/java/Hamming.java @@ -0,0 +1,36 @@ +import java.util.stream.IntStream; + +/** Optimal solution with calculation delegated from constructor. */ +class Hamming { + private final int hammingDistance; + + Hamming(String leftStrand, String rightStrand) { + validateStrandsHaveEqualLength(leftStrand, rightStrand); + + hammingDistance = calculateDistance(leftStrand, rightStrand); + } + + private int calculateDistance(String leftStrand, String rightStrand) { + return (int) IntStream.range(0, leftStrand.length()) + .filter(index -> leftStrand.charAt(index) != rightStrand.charAt(index)) + .count(); + } + + private void validateStrandsHaveEqualLength() { + if (leftStrand.length() == rightStrand.length()) { + return; + } + if (leftStrand.isEmpty()) { + throw new IllegalArgumentException("left strand must not be empty."); + } + if (rightStrand.isEmpty()) { + throw new IllegalArgumentException("right strand must not be empty."); + } + throw new IllegalArgumentException( + "leftStrand and rightStrand must be of equal length."); + } + + int getHammingDistance() { + return hammingDistance; + } +} diff --git a/tests/hello-world/README.md b/tests/hello-world/README.md deleted file mode 100644 index d4fede7b..00000000 --- a/tests/hello-world/README.md +++ /dev/null @@ -1 +0,0 @@ -This test is designed to receive no comments at all from the analyzer. diff --git a/tests/hello-world/expected_analysis.json b/tests/hello-world/expected_analysis.json deleted file mode 100644 index 9e26dfee..00000000 --- a/tests/hello-world/expected_analysis.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/tests/hello-world/expected_tags.json b/tests/hello-world/expected_tags.json deleted file mode 100644 index 9e26dfee..00000000 --- a/tests/hello-world/expected_tags.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/tests/hello-world/src/main/java/Greeter.java b/tests/hello-world/src/main/java/Greeter.java deleted file mode 100644 index 1f5a5e00..00000000 --- a/tests/hello-world/src/main/java/Greeter.java +++ /dev/null @@ -1,5 +0,0 @@ -class Greeter { - String getGreeting() { - return "Hello, World!"; - } -} diff --git a/tests/lasagna/exemplar-solution/expected_analysis.json b/tests/lasagna/exemplar-solution/expected_analysis.json index 65bb122a..63e7c9a4 100644 --- a/tests/lasagna/exemplar-solution/expected_analysis.json +++ b/tests/lasagna/exemplar-solution/expected_analysis.json @@ -1,5 +1,11 @@ -{"comments": [{ - "comment": "java.general.exemplar", - "type": "celebratory", - "params": {"exerciseName": "Lasagna"} -}]} \ No newline at end of file +{ + "comments": [ + { + "comment": "java.general.exemplar", + "params": { + "exerciseName": "Lasagna" + }, + "type": "celebratory" + } + ] +} \ No newline at end of file diff --git a/tests/lasagna/exemplar-solution/expected_tags.json b/tests/lasagna/exemplar-solution/expected_tags.json index 9e26dfee..eb25b190 100644 --- a/tests/lasagna/exemplar-solution/expected_tags.json +++ b/tests/lasagna/exemplar-solution/expected_tags.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/lasagna/no-code-reuse/expected_analysis.json b/tests/lasagna/no-code-reuse/expected_analysis.json index 0c027c57..f9f74609 100644 --- a/tests/lasagna/no-code-reuse/expected_analysis.json +++ b/tests/lasagna/no-code-reuse/expected_analysis.json @@ -1,22 +1,25 @@ -{"comments": [ - { - "comment": "java.lasagna.reuse_code", - "type": "actionable", - "params": { - "callingMethod": "remainingMinutesInOven", - "methodToCall": "expectedMinutesInOven" +{ + "comments": [ + { + "comment": "java.lasagna.reuse_code", + "params": { + "callingMethod": "remainingMinutesInOven", + "methodToCall": "expectedMinutesInOven" + }, + "type": "actionable" + }, + { + "comment": "java.lasagna.reuse_code", + "params": { + "callingMethod": "totalTimeInMinutes", + "methodToCall": "preparationTimeInMinutes" + }, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" } - }, - { - "comment": "java.lasagna.reuse_code", - "type": "actionable", - "params": { - "callingMethod": "totalTimeInMinutes", - "methodToCall": "preparationTimeInMinutes" - } - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file + ] +} \ No newline at end of file diff --git a/tests/lasagna/no-code-reuse/expected_tags.json b/tests/lasagna/no-code-reuse/expected_tags.json index 9e26dfee..eb25b190 100644 --- a/tests/lasagna/no-code-reuse/expected_tags.json +++ b/tests/lasagna/no-code-reuse/expected_tags.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/lasagna/todos-not-removed/expected_analysis.json b/tests/lasagna/todos-not-removed/expected_analysis.json index 9c4825d2..58bf320f 100644 --- a/tests/lasagna/todos-not-removed/expected_analysis.json +++ b/tests/lasagna/todos-not-removed/expected_analysis.json @@ -1,10 +1,14 @@ -{"comments": [ - { - "comment": "java.general.remove_todo_comments", - "type": "actionable" - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file +{ + "comments": [ + { + "comment": "java.general.remove_todo_comments", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/tests/lasagna/todos-not-removed/expected_tags.json b/tests/lasagna/todos-not-removed/expected_tags.json index 9e26dfee..eb25b190 100644 --- a/tests/lasagna/todos-not-removed/expected_tags.json +++ b/tests/lasagna/todos-not-removed/expected_tags.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/leap/hard-coded-test-cases/expected_analysis.json b/tests/leap/hard-coded-test-cases/expected_analysis.json index e4934cb5..ee54550b 100644 --- a/tests/leap/hard-coded-test-cases/expected_analysis.json +++ b/tests/leap/hard-coded-test-cases/expected_analysis.json @@ -1,14 +1,19 @@ -{"comments": [ - { - "comment": "java.general.avoid_hard_coded_test_cases", - "type": "essential" - }, - { - "comment": "java.leap.use_minimum_number_of_checks", - "type": "actionable" - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file +{ + "comments": [ + { + "comment": "java.general.avoid_hard_coded_test_cases", + "params": {}, + "type": "essential" + }, + { + "comment": "java.leap.use_minimum_number_of_checks", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/tests/leap/hard-coded-test-cases/expected_tags.json b/tests/leap/hard-coded-test-cases/expected_tags.json index 9e26dfee..eb25b190 100644 --- a/tests/leap/hard-coded-test-cases/expected_tags.json +++ b/tests/leap/hard-coded-test-cases/expected_tags.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/leap/optimal-solution/expected_analysis.json b/tests/leap/optimal-solution/expected_analysis.json index 9e26dfee..b27d8505 100644 --- a/tests/leap/optimal-solution/expected_analysis.json +++ b/tests/leap/optimal-solution/expected_analysis.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "comments": [] +} \ No newline at end of file diff --git a/tests/leap/optimal-solution/expected_tags.json b/tests/leap/optimal-solution/expected_tags.json index 9e26dfee..eb25b190 100644 --- a/tests/leap/optimal-solution/expected_tags.json +++ b/tests/leap/optimal-solution/expected_tags.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/leap/using-java-time/expected_analysis.json b/tests/leap/using-java-time/expected_analysis.json index d7466506..a5ce5c42 100644 --- a/tests/leap/using-java-time/expected_analysis.json +++ b/tests/leap/using-java-time/expected_analysis.json @@ -1,10 +1,14 @@ -{"comments": [ - { - "comment": "java.leap.no_built_in_methods", - "type": "essential" - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file +{ + "comments": [ + { + "comment": "java.leap.no_built_in_methods", + "params": {}, + "type": "essential" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/tests/leap/using-java-time/expected_tags.json b/tests/leap/using-java-time/expected_tags.json index 9e26dfee..eb25b190 100644 --- a/tests/leap/using-java-time/expected_tags.json +++ b/tests/leap/using-java-time/expected_tags.json @@ -1 +1,3 @@ -{} \ No newline at end of file +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/unknown-exercise/.meta/config.json b/tests/other/unknown-exercise/.meta/config.json similarity index 100% rename from tests/unknown-exercise/.meta/config.json rename to tests/other/unknown-exercise/.meta/config.json diff --git a/tests/unknown-exercise/README.md b/tests/other/unknown-exercise/README.md similarity index 100% rename from tests/unknown-exercise/README.md rename to tests/other/unknown-exercise/README.md diff --git a/tests/other/unknown-exercise/expected_analysis.json b/tests/other/unknown-exercise/expected_analysis.json new file mode 100644 index 00000000..97819c58 --- /dev/null +++ b/tests/other/unknown-exercise/expected_analysis.json @@ -0,0 +1,19 @@ +{ + "comments": [ + { + "comment": "java.general.do_not_use_main_method", + "params": {}, + "type": "essential" + }, + { + "comment": "java.general.avoid_print_statements", + "params": {}, + "type": "informative" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/tests/other/unknown-exercise/expected_tags.json b/tests/other/unknown-exercise/expected_tags.json new file mode 100644 index 00000000..eb25b190 --- /dev/null +++ b/tests/other/unknown-exercise/expected_tags.json @@ -0,0 +1,3 @@ +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/unknown-exercise/src/main/java/UnknownExercise.java b/tests/other/unknown-exercise/src/main/java/UnknownExercise.java similarity index 100% rename from tests/unknown-exercise/src/main/java/UnknownExercise.java rename to tests/other/unknown-exercise/src/main/java/UnknownExercise.java diff --git a/tests/two-fer/README.md b/tests/two-fer/README.md deleted file mode 100644 index 918dc2c4..00000000 --- a/tests/two-fer/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This test is designed to receive comments from multiple analyzers: - -- The global analyzer should complain about the use of a `main` method and print statements -- The `two-fer` analyzer should complain about multiple return statements. diff --git a/tests/two-fer/expected_analysis.json b/tests/two-fer/expected_analysis.json deleted file mode 100644 index 63d1d831..00000000 --- a/tests/two-fer/expected_analysis.json +++ /dev/null @@ -1,18 +0,0 @@ -{"comments": [ - { - "comment": "java.general.do_not_use_main_method", - "type": "essential" - }, - { - "comment": "java.two-fer.avoid_string_format", - "type": "actionable" - }, - { - "comment": "java.general.avoid_print_statements", - "type": "informative" - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file diff --git a/tests/two-fer/expected_tags.json b/tests/two-fer/expected_tags.json deleted file mode 100644 index 9e26dfee..00000000 --- a/tests/two-fer/expected_tags.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/tests/two-fer/non-optimal-solution/.meta/config.json b/tests/two-fer/non-optimal-solution/.meta/config.json new file mode 100644 index 00000000..e5238df8 --- /dev/null +++ b/tests/two-fer/non-optimal-solution/.meta/config.json @@ -0,0 +1,39 @@ +{ + "authors": [ + "Smarticles101" + ], + "contributors": [ + "FridaTveit", + "ikhadykin", + "jmrunkle", + "jssander", + "kytrinyx", + "lemoncurry", + "msomji", + "muzimuzhi", + "rdavid1099", + "sjwarner-bp", + "SleeplessByte", + "sshine", + "stkent", + "uzilan", + "Valkryst", + "ymoskovits" + ], + "files": { + "solution": [ + "src/main/java/Twofer.java" + ], + "test": [ + "src/test/java/TwoferTest.java" + ], + "example": [ + ".meta/src/reference/java/Twofer.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Create a sentence of the form \"One for X, one for me.\".", + "source_url": "https://github.com/exercism/problem-specifications/issues/757" +} \ No newline at end of file diff --git a/tests/two-fer/non-optimal-solution/expected_analysis.json b/tests/two-fer/non-optimal-solution/expected_analysis.json new file mode 100644 index 00000000..184c68eb --- /dev/null +++ b/tests/two-fer/non-optimal-solution/expected_analysis.json @@ -0,0 +1,14 @@ +{ + "comments": [ + { + "comment": "java.two-fer.avoid_string_format", + "params": {}, + "type": "actionable" + }, + { + "comment": "java.general.feedback_request", + "params": {}, + "type": "informative" + } + ] +} \ No newline at end of file diff --git a/tests/two-fer/non-optimal-solution/expected_tags.json b/tests/two-fer/non-optimal-solution/expected_tags.json new file mode 100644 index 00000000..eb25b190 --- /dev/null +++ b/tests/two-fer/non-optimal-solution/expected_tags.json @@ -0,0 +1,3 @@ +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/two-fer/src/main/java/Twofer.java b/tests/two-fer/non-optimal-solution/src/main/java/Twofer.java similarity index 52% rename from tests/two-fer/src/main/java/Twofer.java rename to tests/two-fer/non-optimal-solution/src/main/java/Twofer.java index c780a4f8..87484bee 100644 --- a/tests/two-fer/src/main/java/Twofer.java +++ b/tests/two-fer/non-optimal-solution/src/main/java/Twofer.java @@ -6,10 +6,4 @@ public String twofer(String name) { return String.format("Two for %s, two for me.", name); } - - public static void main(String[] args) { - TwoFer twoFer = new TwoFer(); - System.out.println(twoFer.twofer(null)); - System.out.println(twoFer.twofer("John")); - } } diff --git a/tests/two-fer/optimal-solution/.meta/config.json b/tests/two-fer/optimal-solution/.meta/config.json new file mode 100644 index 00000000..e5238df8 --- /dev/null +++ b/tests/two-fer/optimal-solution/.meta/config.json @@ -0,0 +1,39 @@ +{ + "authors": [ + "Smarticles101" + ], + "contributors": [ + "FridaTveit", + "ikhadykin", + "jmrunkle", + "jssander", + "kytrinyx", + "lemoncurry", + "msomji", + "muzimuzhi", + "rdavid1099", + "sjwarner-bp", + "SleeplessByte", + "sshine", + "stkent", + "uzilan", + "Valkryst", + "ymoskovits" + ], + "files": { + "solution": [ + "src/main/java/Twofer.java" + ], + "test": [ + "src/test/java/TwoferTest.java" + ], + "example": [ + ".meta/src/reference/java/Twofer.java" + ], + "invalidator": [ + "build.gradle" + ] + }, + "blurb": "Create a sentence of the form \"One for X, one for me.\".", + "source_url": "https://github.com/exercism/problem-specifications/issues/757" +} \ No newline at end of file diff --git a/tests/two-fer/optimal-solution/expected_analysis.json b/tests/two-fer/optimal-solution/expected_analysis.json new file mode 100644 index 00000000..b27d8505 --- /dev/null +++ b/tests/two-fer/optimal-solution/expected_analysis.json @@ -0,0 +1,3 @@ +{ + "comments": [] +} \ No newline at end of file diff --git a/tests/two-fer/optimal-solution/expected_tags.json b/tests/two-fer/optimal-solution/expected_tags.json new file mode 100644 index 00000000..eb25b190 --- /dev/null +++ b/tests/two-fer/optimal-solution/expected_tags.json @@ -0,0 +1,3 @@ +{ + "tags": [] +} \ No newline at end of file diff --git a/tests/two-fer/optimal-solution/src/main/java/Twofer.java b/tests/two-fer/optimal-solution/src/main/java/Twofer.java new file mode 100644 index 00000000..a7c531c1 --- /dev/null +++ b/tests/two-fer/optimal-solution/src/main/java/Twofer.java @@ -0,0 +1,5 @@ +class Twofer { + public String twofer(String name) { + return "Two for " + name == null ? "you" : name + ", two for me."; + } +} diff --git a/tests/unknown-exercise/expected_analysis.json b/tests/unknown-exercise/expected_analysis.json deleted file mode 100644 index 91c83c29..00000000 --- a/tests/unknown-exercise/expected_analysis.json +++ /dev/null @@ -1,14 +0,0 @@ -{"comments": [ - { - "comment": "java.general.do_not_use_main_method", - "type": "essential" - }, - { - "comment": "java.general.avoid_print_statements", - "type": "informative" - }, - { - "comment": "java.general.feedback_request", - "type": "informative" - } -]} \ No newline at end of file diff --git a/tests/unknown-exercise/expected_tags.json b/tests/unknown-exercise/expected_tags.json deleted file mode 100644 index 9e26dfee..00000000 --- a/tests/unknown-exercise/expected_tags.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file