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