Skip to content

Commit 3008a6b

Browse files
Kimmo Linnavuod471061c
authored andcommitted
Add points parsing for QTests
Introduced a macro for defining points in test functions. A list of defined points for a test function is now parsed out of the source files.
1 parent 1a535c1 commit 3008a6b

File tree

3 files changed

+95
-47
lines changed

3 files changed

+95
-47
lines changed

tmc-langs-qmake/src/main/java/fi/helsinki/cs/tmc/langs/qmake/QmakePlugin.java

Lines changed: 88 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,34 @@
2727
import org.slf4j.LoggerFactory;
2828

2929
import java.io.File;
30+
import java.io.IOException;
3031
import java.nio.charset.StandardCharsets;
32+
import java.nio.file.FileVisitResult;
3133
import java.nio.file.Files;
3234
import java.nio.file.Path;
3335
import java.nio.file.Paths;
36+
import java.nio.file.SimpleFileVisitor;
37+
import java.nio.file.attribute.BasicFileAttributes;
3438
import java.util.ArrayList;
3539
import java.util.Arrays;
40+
import java.util.HashMap;
3641
import java.util.List;
3742
import java.util.Locale;
3843
import java.util.Map;
44+
import java.util.regex.MatchResult;
45+
import java.util.regex.Matcher;
46+
import java.util.regex.Pattern;
3947

4048
public final class QmakePlugin extends AbstractLanguagePlugin {
4149

42-
private static final Path TEST_DIR = Paths.get("test");
4350
private static final Path TMC_TEST_RESULTS = Paths.get("tmc_test_results.xml");
4451

52+
// POINT(exercise_name, 1)
53+
private static final Pattern POINT_PATTERN
54+
= Pattern.compile("POINT\\(\\s*(\\w+),\\s*(\\w+)\\s*\\)\\s*;");
55+
private static final Pattern COMMENT_PATTERN
56+
= Pattern.compile("(^[^\"\\r\\n]*\\/\\*{1,2}.*?\\*\\/|(^[^\"\\r\\n]*\\/\\/[^\\r\\n]*))", Pattern.MULTILINE | Pattern.DOTALL);
57+
4558
private static final RunResult EMPTY_FAILURE
4659
= new RunResult(
4760
Status.COMPILE_FAILED,
@@ -71,7 +84,8 @@ public String getPluginName() {
7184
* be named after the directory.
7285
*/
7386
private Path getProFile(Path basePath) {
74-
return basePath.resolve(new File(basePath.toFile().getName() + ".pro").toPath());
87+
Path proFile = new File(basePath.toFile().getName() + ".pro").toPath();
88+
return basePath.resolve(proFile);
7589
}
7690

7791
@Override
@@ -81,43 +95,13 @@ public Optional<ExerciseDesc> scanExercise(Path path, String exerciseName) {
8195
return Optional.absent();
8296
}
8397

84-
try {
85-
runTests(path);
86-
} catch (Exception e) {
87-
log.error("Failed to run tests {}", e);
98+
Optional<ImmutableList<TestDesc>> tests = availablePoints(path);
99+
if (!tests.isPresent()) {
100+
log.error("Failed to scan exercise due to parsing error.");
88101
return Optional.absent();
89102
}
90103

91-
return Optional.of(parseExerciseDesc(path, exerciseName));
92-
}
93-
94-
private ExerciseDesc parseExerciseDesc(Path testResults, String exerciseName) {
95-
List<TestDesc> tests = createTestDescs(testResults);
96-
return new ExerciseDesc(exerciseName, ImmutableList.copyOf(tests));
97-
}
98-
99-
private List<TestDesc> createTestDescs(Path testResults) {
100-
List<TestDesc> tests = new ArrayList<>();
101-
List<String> addedTests = new ArrayList<>();
102-
103-
List<TestResult> testCases = readTestResults(testResults);
104-
105-
for (int i = 0; i < testCases.size(); i++) {
106-
TestResult testCase = testCases.get(i);
107-
108-
String testClass = testCase.getName();
109-
String testMethod = testCase.getName();
110-
String testName = testClass + "." + testMethod;
111-
112-
List<String> points = Arrays.asList(testCase.getMessage().split(""));
113-
114-
if (!addedTests.contains(testName)) {
115-
tests.add(new TestDesc(testName, ImmutableList.copyOf(points)));
116-
addedTests.add(testName);
117-
}
118-
}
119-
120-
return tests;
104+
return Optional.of(new ExerciseDesc(exerciseName, ImmutableList.copyOf(tests.get())));
121105
}
122106

123107
@Override
@@ -183,13 +167,6 @@ private Optional<RunResult> build(Path path) {
183167
return Optional.absent();
184168
}
185169

186-
private List<TestResult> readTestResults(Path testPath) {
187-
Path baseTestPath = testPath.toAbsolutePath().resolve(TEST_DIR);
188-
Path testResults = baseTestPath.resolve(TMC_TEST_RESULTS);
189-
190-
return new QTestResultParser(testResults).getTestResults();
191-
}
192-
193170
@Override
194171
public ValidationResult checkCodeStyle(Path path, Locale locale) {
195172
return new ValidationResult() {
@@ -263,4 +240,72 @@ private RunResult filledFailure(ProcessResult processResult) {
263240
.<String, byte[]>build();
264241
return new RunResult(Status.COMPILE_FAILED, ImmutableList.<TestResult>of(), logs);
265242
}
243+
244+
/**
245+
* TODO: This is copy paste from tmc-langs regex branch. Regex branch method
246+
* 'availablePoints' does not parse test names at this time.
247+
*
248+
* POINT_PATTERN matches the #define macro in the tests, for example:
249+
*
250+
* POINT(test_name2, suite_point);
251+
*
252+
* POINT(test_name, 1);
253+
*
254+
* etc.
255+
*/
256+
public Optional<ImmutableList<TestDesc>> availablePoints(final Path rootPath) {
257+
return findAvailablePoints(rootPath, POINT_PATTERN, COMMENT_PATTERN, ".cpp");
258+
}
259+
260+
protected Optional<ImmutableList<TestDesc>> findAvailablePoints(final Path rootPath,
261+
Pattern pattern, Pattern commentPattern, String suffix) {
262+
Map<String, List<String>> tests = new HashMap<>();
263+
List<TestDesc> descs = new ArrayList<>();
264+
try {
265+
final List<Path> potentialPointFiles = getPotentialPointFiles(rootPath, suffix);
266+
for (Path p : potentialPointFiles) {
267+
268+
String contents = new String(Files.readAllBytes(p), "UTF-8");
269+
String withoutComments = commentPattern.matcher(contents).replaceAll("");
270+
Matcher matcher = pattern.matcher(withoutComments);
271+
while (matcher.find()) {
272+
MatchResult matchResult = matcher.toMatchResult();
273+
String testName = matchResult.group(1).trim();
274+
String point = matchResult.group(2).trim();
275+
tests.putIfAbsent(testName, new ArrayList<String>());
276+
tests.get(testName).add(point);
277+
}
278+
}
279+
for (String testName : tests.keySet()) {
280+
List<String> points = tests.getOrDefault(testName, new ArrayList<String>());
281+
descs.add(new TestDesc(testName, ImmutableList.<String>copyOf(points)));
282+
}
283+
return Optional.of(ImmutableList.copyOf(descs));
284+
} catch (IOException e) {
285+
// We could scan the exercise but that could be a security risk
286+
return Optional.absent();
287+
}
288+
}
289+
290+
private List<Path> getPotentialPointFiles(final Path rootPath, final String suffix)
291+
throws IOException {
292+
final StudentFilePolicy studentFilePolicy = getStudentFilePolicy(rootPath);
293+
final List<Path> potentialPointFiles = new ArrayList<>();
294+
295+
Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
296+
@Override
297+
public FileVisitResult visitFile(Path path, BasicFileAttributes attrs)
298+
throws IOException {
299+
if (studentFilePolicy.isStudentFile(path, rootPath)) {
300+
return FileVisitResult.SKIP_SUBTREE;
301+
}
302+
if (!Files.isDirectory(path) && path.toString().endsWith(suffix)) {
303+
potentialPointFiles.add(path);
304+
}
305+
return FileVisitResult.CONTINUE;
306+
}
307+
});
308+
309+
return potentialPointFiles;
310+
}
266311
}

tmc-langs-qmake/src/main/java/fi/helsinki/cs/tmc/langs/qmake/QmakeStudentFilePolicy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
public final class QmakeStudentFilePolicy extends ConfigurableStudentFilePolicy {
99

10-
private static final Path MAKEFILE_PATH = Paths.get("Makefile");
10+
// TODO: figure this out
1111
private static final Path SOURCE_FOLDER_PATH = Paths.get("src");
1212

1313
public QmakeStudentFilePolicy(Path configFileParent) {

tmc-langs-qmake/src/test/resources/failing_single_lib_same_point/test_case_test_runner/test_case_test_runner.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
#include "test_case_test_runner.h"
33
#include "test_case_lib.h"
44

5+
// Produces qInfo("TMC:test_name.point")
6+
#define POINT(test_name, point) qInfo("TMC:"#test_name"."#point)
7+
58
test_case_test_runner::test_case_test_runner(QObject *parent) : QObject(parent)
69
{
710

@@ -11,7 +14,7 @@ void test_case_test_runner::test_function_one_here() {
1114

1215
test_case_lib test_case;
1316

14-
qInfo("TMC:test_function_one_here.1");
17+
POINT(test_function_one_here, 1);
1518
QVERIFY(!strcmp(test_case.piece_of_string(), "Hello, world!"));
1619

1720
}
@@ -20,7 +23,7 @@ void test_case_test_runner::test_function_two_here() {
2023

2124
test_case_lib test_case;
2225

23-
qInfo("TMC:test_function_two_here.2");
26+
POINT(test_function_two_here, 2);
2427
QVERIFY(test_case.adding_ints(0, 0) == 0);
2528

2629
}
@@ -29,6 +32,6 @@ void test_case_test_runner::test_function_two_here_2() {
2932

3033
test_case_lib test_case;
3134

32-
qInfo("TMC:test_function_two_here_2.1");
35+
POINT(test_function_two_here_2, 1);
3336
QVERIFY(test_case.adding_ints(-341, 428) == 87);
3437
}

0 commit comments

Comments
 (0)