-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Branch for working on rust support #12 #28
Changes from 11 commits
9916503
e2af326
8ee8782
9977493
282b371
8410279
68cef05
348bfda
3eac0b0
1544224
3ce6146
5ea8fda
2326b2e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package fi.helsinki.cs.tmc.langs.rust; | ||
|
||
import fi.helsinki.cs.tmc.langs.AbstractLanguagePlugin; | ||
import fi.helsinki.cs.tmc.langs.abstraction.ValidationResult; | ||
import fi.helsinki.cs.tmc.langs.domain.Configuration; | ||
import fi.helsinki.cs.tmc.langs.domain.ExerciseBuilder; | ||
import fi.helsinki.cs.tmc.langs.domain.ExerciseDesc; | ||
import fi.helsinki.cs.tmc.langs.domain.RunResult; | ||
import fi.helsinki.cs.tmc.langs.domain.RunResult.Status; | ||
import fi.helsinki.cs.tmc.langs.domain.SpecialLogs; | ||
import fi.helsinki.cs.tmc.langs.domain.TestResult; | ||
import fi.helsinki.cs.tmc.langs.io.StudentFilePolicy; | ||
import fi.helsinki.cs.tmc.langs.io.sandbox.StudentFileAwareSubmissionProcessor; | ||
import fi.helsinki.cs.tmc.langs.io.zip.StudentFileAwareUnzipper; | ||
import fi.helsinki.cs.tmc.langs.io.zip.StudentFileAwareZipper; | ||
import fi.helsinki.cs.tmc.langs.rust.util.Constants; | ||
import fi.helsinki.cs.tmc.langs.utils.ProcessResult; | ||
import fi.helsinki.cs.tmc.langs.utils.ProcessRunner; | ||
|
||
import com.google.common.base.Optional; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.nio.file.Files; | ||
import java.nio.file.Path; | ||
import java.util.Arrays; | ||
|
||
public class CargoPlugin extends AbstractLanguagePlugin { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(CargoPlugin.class); | ||
private static final RunResult EMPTY_FAILURE = new RunResult( | ||
Status.COMPILE_FAILED, | ||
ImmutableList.<TestResult>of(), | ||
new ImmutableMap.Builder<String, byte[]>().build()); | ||
|
||
public CargoPlugin() { | ||
super( | ||
new ExerciseBuilder(), | ||
new StudentFileAwareSubmissionProcessor(), | ||
new StudentFileAwareZipper(), | ||
new StudentFileAwareUnzipper()); | ||
} | ||
|
||
@Override | ||
public boolean isExerciseTypeCorrect(Path path) { | ||
return Files.isRegularFile(path.resolve(Constants.CARGO_TOML)); | ||
} | ||
|
||
@Override | ||
protected StudentFilePolicy getStudentFilePolicy(Path projectPath) { | ||
return new CargoStudentFilePolicy(projectPath); | ||
} | ||
|
||
@Override | ||
public ValidationResult checkCodeStyle(Path path) { | ||
throw new UnsupportedOperationException("Not supported yet."); | ||
} | ||
|
||
@Override | ||
public String getLanguageName() { | ||
return "cargo"; | ||
} | ||
|
||
public String getPluginName() { | ||
return "cargo"; | ||
} | ||
|
||
@Override | ||
public Optional<ExerciseDesc> scanExercise(Path path, String exerciseName) { | ||
throw new UnsupportedOperationException("Not supported yet."); | ||
} | ||
|
||
@Override | ||
public RunResult runTests(Path path) { | ||
Optional<RunResult> result = build(path); | ||
if (result.isPresent()) { | ||
log.info("Failed to compile project."); | ||
return result.get(); | ||
} | ||
return runBuiltTests(path); | ||
} | ||
|
||
private Optional<RunResult> build(Path path) { | ||
String[] command = {"cargo", "test", "--no-run"}; | ||
log.info("Building project with command {0}", Arrays.deepToString(command)); | ||
Optional<ProcessResult> result = run(command, path); | ||
if (result.isPresent()) { | ||
if (result.get().statusCode == 0) { | ||
return Optional.absent(); | ||
} | ||
return Optional.of(filledFailure(result.get())); | ||
} | ||
return Optional.of(EMPTY_FAILURE); | ||
} | ||
|
||
private RunResult runBuiltTests(Path dir) { | ||
String[] command = {"cargo", "test"}; | ||
log.info("Running tests with command {0}", Arrays.deepToString(command)); | ||
Optional<ProcessResult> result = run(command, dir); | ||
if (result.isPresent()) { | ||
return parseResult(result.get(), dir); | ||
} | ||
return EMPTY_FAILURE; | ||
} | ||
|
||
private Optional<ProcessResult> run(String[] command, Path dir) { | ||
ProcessRunner runner = new ProcessRunner(command, dir); | ||
try { | ||
return Optional.of(runner.call()); | ||
} catch (Exception e) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's just catch relevant exceptions and at least log those There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well the call function actually just throws an Exception... I'll add logging there though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps also log which command failed, since we could theoretically be running multiple concurrent runs? |
||
log.error("Running command {0} failed {1}", Arrays.deepToString(command), e); | ||
return Optional.absent(); | ||
} | ||
} | ||
|
||
private RunResult filledFailure(ProcessResult processResult) { | ||
byte[] errorOutput = processResult.errorOutput.getBytes(StandardCharsets.UTF_8); | ||
ImmutableMap<String, byte[]> logs = new ImmutableMap.Builder() | ||
.put(SpecialLogs.COMPILER_OUTPUT, errorOutput) | ||
.<String, byte[]>build(); | ||
return new RunResult( | ||
Status.COMPILE_FAILED, | ||
ImmutableList.<TestResult>of(), | ||
logs); | ||
} | ||
|
||
private RunResult parseResult(ProcessResult processResult, Path path) { | ||
return new CargoResultParser().parse(processResult); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package fi.helsinki.cs.tmc.langs.rust; | ||
|
||
import fi.helsinki.cs.tmc.langs.domain.RunResult; | ||
import fi.helsinki.cs.tmc.langs.domain.RunResult.Status; | ||
import fi.helsinki.cs.tmc.langs.domain.SpecialLogs; | ||
import fi.helsinki.cs.tmc.langs.domain.TestResult; | ||
import fi.helsinki.cs.tmc.langs.utils.ProcessResult; | ||
|
||
import com.google.common.base.Optional; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* Parses Cargos test output. | ||
*/ | ||
public class CargoResultParser { | ||
|
||
private static final RunResult PARSING_FAILED = new RunResult( | ||
Status.GENERIC_ERROR, | ||
ImmutableList.<TestResult>of(), | ||
new ImmutableMap.Builder<String, byte[]>() | ||
.put("generic_error_message", "Test results couldn't be parsed." | ||
.getBytes(StandardCharsets.UTF_8)) | ||
.build()); | ||
|
||
//test result: FAILED. 25 passed; 1 failed; 0 ignored; 0 measured | ||
private static final Pattern RESULT | ||
= Pattern.compile("test result: .*\\. (?<passes>\\d*) passed; (?<fails>\\d*) failed; \\d* ignored; \\d* measured"); | ||
//test test::dim4::test_4d_1_80_perioditic ... ok | ||
private static final Pattern TEST = Pattern.compile("test (?<name>.*) \\.\\.\\. .*"); | ||
//thread 'test::dim2::test_2d_1_80_normal' panicked at 'assertion failed: false', src\test\dim2.rs:12 | ||
private static final Pattern FAILURES | ||
= Pattern.compile(".*thread '(?<name>.*)' panicked at '(?<description>.*)', .*"); | ||
|
||
/** | ||
* Parses given process results outputs to a run result. | ||
*/ | ||
public RunResult parse(ProcessResult processResult) { | ||
String output = processResult.output; | ||
String[] lines = output.split("\\r?\\n"); | ||
|
||
Matcher matcher = RESULT.matcher(output); | ||
while (matcher.find()) { | ||
int fails = Integer.parseInt(matcher.group("fails")); | ||
int passes = Integer.parseInt(matcher.group("passes")); | ||
if (fails + passes > 0) { | ||
Optional<Map<String, String>> failures | ||
= findFailures(processResult.output, fails); | ||
if (failures.isPresent()) { | ||
Status status = fails == 0 ? Status.PASSED : Status.TESTS_FAILED; | ||
List<String> tests = parseResults(lines); | ||
return new RunResult( | ||
status, | ||
buildTestResults(tests, failures.get()), | ||
new ImmutableMap.Builder() | ||
.put(SpecialLogs.STDOUT, | ||
processResult.output.getBytes(StandardCharsets.UTF_8)) | ||
.put(SpecialLogs.STDERR, | ||
processResult.errorOutput.getBytes(StandardCharsets.UTF_8)) | ||
.build()); | ||
} | ||
break; | ||
} | ||
} | ||
return PARSING_FAILED; | ||
} | ||
|
||
private List<String> parseResults(String[] lines) { | ||
List<String> result = new ArrayList<>(); | ||
for (String line : lines) { | ||
Matcher matcher = TEST.matcher(line); | ||
if (matcher.matches()) { | ||
result.add(matcher.group("name")); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
private Optional<Map<String, String>> findFailures(String errorOutput, int fails) { | ||
Map<String, String> result = new HashMap<>(); | ||
String[] lines = errorOutput.split("\\r?\\n"); | ||
for (String line : lines) { | ||
Matcher matcher = FAILURES.matcher(line); | ||
if (matcher.matches()) { | ||
result.put(matcher.group("name"), matcher.group("description")); | ||
} | ||
} | ||
return Optional.of(result); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will never return |
||
} | ||
|
||
private ImmutableList<TestResult> buildTestResults(List<String> results, | ||
Map<String, String> failures) { | ||
ImmutableList.Builder<TestResult> testResults = ImmutableList.builder(); | ||
for (String test : results) { | ||
String description = failures.get(test); | ||
boolean pass = false; | ||
if (description == null) { | ||
description = ""; | ||
pass = true; | ||
} | ||
TestResult testResult = new TestResult( | ||
test, | ||
pass, | ||
ImmutableList.<String>of(), | ||
description, | ||
ImmutableList.<String>of()); | ||
testResults.add(testResult); | ||
} | ||
return testResults.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package fi.helsinki.cs.tmc.langs.rust; | ||
|
||
import fi.helsinki.cs.tmc.langs.io.ConfigurableStudentFilePolicy; | ||
import fi.helsinki.cs.tmc.langs.rust.util.Constants; | ||
|
||
import java.nio.file.Path; | ||
|
||
public class CargoStudentFilePolicy extends ConfigurableStudentFilePolicy { | ||
|
||
public CargoStudentFilePolicy(Path configFileParent) { | ||
super(configFileParent); | ||
} | ||
|
||
/** | ||
* Returns {@code true} for all files in the <tt>projectRoot/src</tt> directory and other | ||
* files required for building the project. | ||
* | ||
* <p>Will NOT return {@code true} for any test files. If test file modification are part | ||
* of the exercise, those test files are whitelisted as <tt>ExtraStudentFiles</tt> and the | ||
* decision to move them is made by {@link ConfigurableStudentFilePolicy}. | ||
*/ | ||
@Override | ||
public boolean isStudentSourceFile(Path path) { | ||
return !path.endsWith(Constants.CARGO_TOML) && path.startsWith(Constants.SOURCE); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
package fi.helsinki.cs.tmc.langs.rust.util; | ||
|
||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
|
||
public class Constants { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be package private and final? Also consider renaming this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You cannot make it package private because it's used outside of util. Also I prefer not adding qualifiers to names which are apparent from the package (Package + class name was supposed to be the unique identifier in Java). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't really like adding any more public classes (we need to more aggressively start changing structure so that we can manage with package visible classes.) However for now this is fine. |
||
public static final Path CARGO_TOML = Paths.get("Cargo.toml"); | ||
public static final Path SOURCE = Paths.get("src"); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure how it is currently done elsewhere but returning empty value instead of error/exception omits the error (or is this just backup solution...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you mean here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How it would look to the end user if the
cargo test
failsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cargo test
failing here would mean that there was an IOException while running the command. I am not sure what situation this would happen that wouldn't already happen while building tests. And compilation output when compilation failure happens is already preserved.Dunno how to how to deal this.