Skip to content

Commit

Permalink
Merge pull request #28 from WaDelma/rust
Browse files Browse the repository at this point in the history
Added initial support for Rust.
  • Loading branch information
jamo committed Sep 13, 2015
2 parents 78e97c9 + 2326b2e commit f8b1506
Show file tree
Hide file tree
Showing 26 changed files with 591 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ addons:
- check
- python3.4
before_install:
- curl -L https://static.rust-lang.org/rustup.sh | sh -s -- --channel=stable --yes --prefix=$PWD --disable-sudo
- export PATH=$PATH:$PWD/bin
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib
- mkdir $HOME/bin && ln -s $(which python3.4) $HOME/bin/python3 && export PATH="$HOME/bin:$PATH"
script:
- mvn checkstyle:check
Expand Down
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) {
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,115 @@
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) {
Map<String, String> failures = findFailures(processResult.output, fails);
Status status = fails == 0 ? Status.PASSED : Status.TESTS_FAILED;
List<String> tests = parseResults(lines);
return new RunResult(
status,
buildTestResults(tests, failures),
new ImmutableMap.Builder()
.put(SpecialLogs.STDOUT,
processResult.output.getBytes(StandardCharsets.UTF_8))
.put(SpecialLogs.STDERR,
processResult.errorOutput.getBytes(StandardCharsets.UTF_8))
.build());
}
}
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 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 result;
}

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 {
public static final Path CARGO_TOML = Paths.get("Cargo.toml");
public static final Path SOURCE = Paths.get("src");
}
Loading

0 comments on commit f8b1506

Please sign in to comment.