Skip to content

Commit fcadc88

Browse files
committed
wip for PIT
1 parent 1e50187 commit fcadc88

4 files changed

Lines changed: 212 additions & 0 deletions

File tree

build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ dependencies {
5858
implementation 'com.github.bsommerfeld.jshepherd:yaml:3.3.2'
5959
implementation 'com.google.flogger:flogger:0.9'
6060
implementation 'com.google.flogger:flogger-system-backend:0.9'
61+
implementation 'org.pitest:pitest:1.20.6'
62+
implementation 'org.pitest:pitest-entry:1.20.6'
63+
implementation 'org.pitest:pitest-html-report:1.20.6'
6164

6265
// Annotations
6366
annotationProcessor 'com.google.dagger:dagger-compiler:2.57.2'
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,45 @@
11
package chalkbox.commands;
22

3+
import chalkbox.config.Config;
4+
import chalkbox.source.Solution;
5+
import chalkbox.source.Submission;
6+
import chalkbox.stages.StageException;
7+
import com.google.common.flogger.FluentLogger;
8+
import de.bsommerfeld.jshepherd.core.ConfigurationLoader;
39
import picocli.CommandLine.Command;
410
import picocli.CommandLine.Mixin;
511

12+
import java.nio.file.Path;
13+
import java.nio.file.Paths;
14+
615
@Command(name = "mutation",
716
description = "Runs mutation over the input project")
817
public class Mutation implements Runnable {
18+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
19+
920
@Mixin Shared shared = new Shared();
1021

1122
@Override
1223
public void run() {
24+
Path configFile = Paths.get(shared.configFile);
25+
var config = ConfigurationLoader.load(configFile, Config::new);
26+
27+
//todo(mh): Config this
28+
var submission = new Submission(shared.submissionPath, "/home/millie/Documents/projects/chalkbox/test/resources/csse2002/lib/junit-4.12.jar");
1329

30+
var stage = config.toMutation();
31+
try {
32+
var result = stage.run(submission);
33+
logger.atInfo().log("Mutation Run");
34+
logger.atInfo().log(result.overview().getOutput());
35+
for (var inner : result.results()) {
36+
logger.atInfo().log(inner.getOutput());
37+
}
38+
} catch (StageException e) {
39+
logger.atSevere().log(e.toString());
40+
System.exit(0);
41+
} catch (Exception e) {
42+
throw new RuntimeException(e);
43+
}
1444
}
1545
}

src/chalkbox/config/Config.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import chalkbox.stages.functionality.Functionality;
44
import chalkbox.stages.codestyle.CodeStyle;
55
import chalkbox.stages.conformance.Conformance;
6+
import chalkbox.stages.mutation.Mutation;
67
import de.bsommerfeld.jshepherd.annotation.Comment;
78
import de.bsommerfeld.jshepherd.annotation.Key;
89
import de.bsommerfeld.jshepherd.annotation.PostInject;
@@ -52,4 +53,8 @@ public Conformance toConformance() {
5253
public Functionality toFunctionality() {
5354
return new Functionality(38);
5455
}
56+
57+
public Mutation toMutation() {
58+
return new Mutation(38);
59+
}
5560
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package chalkbox.stages.mutation;
2+
3+
import chalkbox.api.common.java.JUnitIndividualResult;
4+
import chalkbox.api.common.java.JUnitRunner;
5+
import chalkbox.source.Solution;
6+
import chalkbox.source.Submission;
7+
import chalkbox.stages.*;
8+
9+
import com.google.common.flogger.FluentLogger;
10+
import org.pitest.mutationtest.config.PluginServices;
11+
import org.pitest.mutationtest.config.ReportOptions;
12+
import org.pitest.mutationtest.tooling.EntryPoint;
13+
import org.pitest.mutationtest.tooling.MutationCoverage;
14+
import org.pitest.testapi.TestGroupConfig;
15+
import org.pitest.util.Glob;
16+
import org.pitest.util.Verbosity;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.Path;
22+
import java.util.*;
23+
import java.util.function.Predicate;
24+
25+
public class Mutation implements Stage {
26+
27+
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
28+
public final static String name = "Mutation";
29+
30+
private final int maxScore;
31+
32+
public Mutation(int maxScore) {
33+
this.maxScore = maxScore;
34+
}
35+
36+
@Override
37+
public StageResult run(Submission submission) throws StageException {
38+
// Compile the solution, tests and the submission
39+
try {
40+
var compilation = submission.compileSrc();
41+
if (!compilation.success()) {
42+
throw new StageException("Unable to compile submission: " + compilation.output());
43+
}
44+
compilation = submission.compileTest();
45+
if (!compilation.success()) {
46+
throw new StageException("Unable to compile submission tests: " + compilation.output());
47+
}
48+
} catch (IOException e) {
49+
throw new RuntimeException(e);
50+
}
51+
52+
List<String> tests = null;
53+
try {
54+
tests = submission.getTestClasses();
55+
} catch (IOException e) {
56+
throw new StageException(e.toString());
57+
}
58+
59+
// // Run tests against the solution
60+
// var classPath = solution.getClassPath() +
61+
// File.pathSeparator + solution.getSrcBuildPath() +
62+
// File.pathSeparator + solution.getTestBuildPath();
63+
// var baselineResults = this.runTests(tests, classPath);
64+
65+
// Path contains dependencies and the compile submission
66+
var classPath = submission.getClassPath() +
67+
File.pathSeparator + submission.getSrcBuildPath() +
68+
File.pathSeparator + submission.getTestBuildPath();
69+
var submissionResults = this.runTests(tests, classPath); //todo(mh): fail here if they dont pass their own tests?
70+
71+
72+
var e = new EntryPoint();
73+
ReportOptions data = new ReportOptions();
74+
// Set the classes to mutate
75+
data.setTargetClasses(Collections.singletonList("tms.sensors.DemoPressurePad"));
76+
77+
// Set the tests to run against the mutations
78+
var packages = new ArrayList<Predicate<String>>();
79+
packages.add(new Glob("*"));
80+
data.setTargetTests(packages);
81+
82+
// Set classpath elements (compiled code, test code, dependencies)
83+
// This is where it gets complicated; you need to find the correct paths.
84+
var path = new ArrayList<String>();
85+
path.add(submission.getClassPath()); //todo(mh): do we need to split by ":", replace with just libs needed
86+
path.add(submission.getSrcBuildPath());
87+
path.add(submission.getTestBuildPath());
88+
String classpath = System.getProperty("java.class.path");
89+
System.out.println("Full Classpath: " + classpath);
90+
91+
// Split the classpath into individual entries
92+
String[] classPathEntries = classpath.split(File.pathSeparator);
93+
path.addAll(List.of(classPathEntries));
94+
data.setClassPathElements(path); // Custom method to get paths
95+
96+
// Set source directories for report generation
97+
var sourceList = new ArrayList<Path>();
98+
sourceList.add(Path.of(submission.getSrcFolder()));
99+
sourceList.add(Path.of(submission.getTestFolder()));
100+
data.setSourceDirs(sourceList);
101+
102+
// Set output directory
103+
data.setReportDir("target/custom-pit-report");
104+
105+
// Set mutators, threads, etc. (optional, defaults are often fine)
106+
data.setMutators(Collections.singletonList("DEFAULTS"));
107+
108+
data.setGroupConfig(new TestGroupConfig());
109+
data.addOutputFormats(Collections.singletonList("HTML"));
110+
data.setOutputEncoding(StandardCharsets.UTF_8);
111+
data.setInputEncoding(StandardCharsets.UTF_8);
112+
data.setVerbosity(Verbosity.VERBOSE);
113+
114+
PluginServices plugins = PluginServices.makeForContextLoader();
115+
var result = e.execute(null, data, plugins, new HashMap<>());
116+
117+
118+
var stats = result.getStatistics().get();
119+
120+
121+
var overview = new Result(name);
122+
// overview.setScore(scaled)
123+
// .setMaxScore(maxScore)
124+
// .appendOutput(table + equation)
125+
// .setOutputFormat("md")
126+
// .setVisibility(Visibility.AFTER_PUBLISH);
127+
128+
return StageResult.fromOverview(overview);
129+
}
130+
131+
@Override
132+
public StageResult run(Submission submission, List<Solution> solutions) throws StageException {
133+
// Not implemented
134+
return null;
135+
}
136+
137+
/**
138+
* Run the tests on a submission.
139+
* <p>
140+
* If there were issues compiling the sample solution or the tests, or
141+
* the submission did not compile successfully, no action is taken.
142+
* <p>
143+
* Uses a JUnit listener to observe the passed/failed tests for each test
144+
* class. One Gradescope test is created for each JUnit test method, with
145+
* a mark of zero if the test failed, or a mark of
146+
* <code>stageWeighting / numTests</code> if the test passed, where
147+
* <code>stageWeighting</code> is the number of marks allocated to this
148+
* stage, and <code>numTests</code> is the total number of JUnit test
149+
* methods in all test classes.
150+
*/
151+
@Override
152+
public StageResult run(Submission submission, Solution solution) throws StageException {
153+
// Not implemented
154+
return null;
155+
}
156+
157+
private Map<String, List<JUnitIndividualResult>> runTests(List<String> tests, String classPath) {
158+
var collection = new HashMap<String, List<JUnitIndividualResult>>();
159+
for (String className : tests) {
160+
// Ignore any that dont end in TEST
161+
if (!className.endsWith("Test")) {
162+
continue;
163+
}
164+
165+
var results = JUnitRunner.runTests(className, classPath);
166+
if (results.isEmpty()) {
167+
continue;
168+
}
169+
results.sort(Comparator.comparing(JUnitIndividualResult::name));
170+
collection.put(className, results);
171+
}
172+
return collection;
173+
}
174+
}

0 commit comments

Comments
 (0)