Skip to content

Commit 76dfbc6

Browse files
authored
Merge pull request #86 from RTMC/master
Added R support to tmc-langs.
2 parents 55e4dfb + 59a58a5 commit 76dfbc6

File tree

32 files changed

+1105
-0
lines changed

32 files changed

+1105
-0
lines changed

.travis.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,18 @@ env:
1919

2020
before_install:
2121
- curl -L https://static.rust-lang.org/rustup.sh | sh -s -- --channel=stable --yes --prefix=$PWD --disable-sudo
22+
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
23+
- echo "deb https://cloud.r-project.org/bin/linux/ubuntu trusty/" | sudo tee -a /etc/apt/sources.list
24+
- sudo apt-get update -qq
25+
- sudo apt-get install r-base -y
26+
- sudo chmod 277 /usr/local/lib/R/site-library
27+
- Rscript -e 'install.packages(c("devtools","testthat"),repos="http://cran.us.r-project.org")'
28+
- Rscript -e 'devtools::install_github("RTMC/tmc-r-tester/tmcRtestrunner")'
2229
- export PATH=$PATH:$PWD/bin
2330
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/lib
2431
- mkdir -p $HOME/bin && ln -s $(which python3.4) $HOME/bin/python3 && export PATH="$HOME/bin:$PATH"
2532
- mvn install -Dmaven.test.skip=true
33+
2634
script:
2735
- mvn clean test
2836
- mvn checkstyle:check

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@
237237

238238
<module>tmc-langs-util</module>
239239
<module>tmc-langs-cli</module>
240+
<module>tmc-langs-r</module>
240241
</modules>
241242

242243
<profiles>

tmc-langs-r/pom.xml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<groupId>fi.helsinki.cs.tmc</groupId>
6+
<artifactId>tmc-langs</artifactId>
7+
<version>0.7.7-SNAPSHOT</version>
8+
</parent>
9+
<artifactId>tmc-langs-r</artifactId>
10+
<packaging>jar</packaging>
11+
12+
<dependencies>
13+
<dependency>
14+
<groupId>fi.helsinki.cs.tmc</groupId>
15+
<artifactId>tmc-langs-framework</artifactId>
16+
<version>${project.version}</version>
17+
</dependency>
18+
<dependency>
19+
<groupId>fi.helsinki.cs.tmc</groupId>
20+
<artifactId>tmc-langs-util</artifactId>
21+
<version>${project.version}</version>
22+
</dependency>
23+
</dependencies>
24+
</project>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package fi.helsinki.cs.tmc.langs.r;
2+
3+
import fi.helsinki.cs.tmc.langs.domain.TestDesc;
4+
5+
import com.fasterxml.jackson.core.type.TypeReference;
6+
import com.fasterxml.jackson.databind.ObjectMapper;
7+
import com.google.common.collect.ImmutableList;
8+
9+
import java.io.IOException;
10+
import java.nio.file.Files;
11+
import java.nio.file.Path;
12+
import java.nio.file.Paths;
13+
import java.util.ArrayList;
14+
import java.util.List;
15+
import java.util.Map;
16+
17+
public class RExerciseDescParser {
18+
19+
private static final Path RESULT_FILE = Paths.get(".available_points.json");
20+
private static final TypeReference<Map<String, List<String>>> MAP_TYPE_REFERENCE =
21+
new TypeReference<Map<String, List<String>>>() {};
22+
private final Path path;
23+
private final ObjectMapper mapper;
24+
25+
public RExerciseDescParser(Path path) {
26+
this.path = path;
27+
this.mapper = new ObjectMapper();
28+
}
29+
30+
public ImmutableList<TestDesc> parse() throws IOException {
31+
List<TestDesc> testDescs = new ArrayList<>();
32+
byte[] json = Files.readAllBytes(path.resolve(RESULT_FILE));
33+
Map<String, List<String>> parse = mapper.readValue(json, MAP_TYPE_REFERENCE);
34+
35+
for (String name : parse.keySet()) {
36+
ImmutableList<String> points = ImmutableList.copyOf(parse.get(name));
37+
testDescs.add(new TestDesc(name, points));
38+
}
39+
40+
return ImmutableList.copyOf(testDescs);
41+
}
42+
}
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
package fi.helsinki.cs.tmc.langs.r;
2+
3+
import fi.helsinki.cs.tmc.langs.AbstractLanguagePlugin;
4+
import fi.helsinki.cs.tmc.langs.abstraction.Strategy;
5+
import fi.helsinki.cs.tmc.langs.abstraction.ValidationError;
6+
import fi.helsinki.cs.tmc.langs.abstraction.ValidationResult;
7+
import fi.helsinki.cs.tmc.langs.domain.ExerciseBuilder;
8+
import fi.helsinki.cs.tmc.langs.domain.ExerciseDesc;
9+
import fi.helsinki.cs.tmc.langs.domain.RunResult;
10+
import fi.helsinki.cs.tmc.langs.domain.SpecialLogs;
11+
import fi.helsinki.cs.tmc.langs.domain.TestDesc;
12+
import fi.helsinki.cs.tmc.langs.domain.TestResult;
13+
import fi.helsinki.cs.tmc.langs.io.StudentFilePolicy;
14+
import fi.helsinki.cs.tmc.langs.io.sandbox.StudentFileAwareSubmissionProcessor;
15+
import fi.helsinki.cs.tmc.langs.io.zip.StudentFileAwareUnzipper;
16+
import fi.helsinki.cs.tmc.langs.io.zip.StudentFileAwareZipper;
17+
import fi.helsinki.cs.tmc.langs.utils.ProcessRunner;
18+
19+
import com.google.common.base.Optional;
20+
import com.google.common.collect.ImmutableList;
21+
import com.google.common.collect.ImmutableMap;
22+
import com.google.common.collect.Maps;
23+
24+
import org.apache.commons.lang3.ArrayUtils;
25+
import org.apache.commons.lang3.SystemUtils;
26+
import org.apache.commons.lang3.exception.ExceptionUtils;
27+
28+
import org.slf4j.Logger;
29+
import org.slf4j.LoggerFactory;
30+
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.nio.file.Files;
34+
import java.nio.file.Path;
35+
import java.nio.file.Paths;
36+
import java.util.ArrayList;
37+
import java.util.HashMap;
38+
import java.util.List;
39+
import java.util.Locale;
40+
import java.util.Map;
41+
42+
public final class RPlugin extends AbstractLanguagePlugin {
43+
44+
/**
45+
* R folder contains the actual R files used in the
46+
* project/package. It is automatically included when creating a
47+
* R package but now when making a regular project in RStudio.
48+
*/
49+
private static final Path R_FOLDER_PATH = Paths.get("R");
50+
51+
/**
52+
* test/testthat folder contains the unit testing
53+
* files which use the testThat library for the R project.
54+
*/
55+
private static final Path TEST_FOLDER_PATH = Paths.get("tests");
56+
private static final Path TESTTHAT_FOLDER_PATH = Paths.get("testthat");
57+
58+
private static final String CANNOT_RUN_TESTS_MESSAGE = "Failed to run tests.";
59+
private static final String CANNOT_PARSE_TEST_RESULTS_MESSAGE = "Failed to read test results.";
60+
private static final String CANNOT_SCAN_EXERCISE_MESSAGE = "Failed to scan exercise.";
61+
private static final String CANNOT_PARSE_EXERCISE_DESCRIPTION_MESSAGE =
62+
"Failed to parse exercise description.";
63+
64+
private static Logger log = LoggerFactory.getLogger(RPlugin.class);
65+
66+
public RPlugin() {
67+
super(
68+
new ExerciseBuilder(),
69+
new StudentFileAwareSubmissionProcessor(),
70+
new StudentFileAwareZipper(),
71+
new StudentFileAwareUnzipper());
72+
}
73+
74+
/**
75+
* NOTE: Files.exists does not seem to be able to verify the R and
76+
* testthat folder's existence if they are empty.
77+
*/
78+
@Override
79+
public boolean isExerciseTypeCorrect(Path path) {
80+
return Files.exists(path.resolve(R_FOLDER_PATH))
81+
|| Files.exists(path.resolve(TEST_FOLDER_PATH).resolve(TESTTHAT_FOLDER_PATH));
82+
}
83+
84+
@Override
85+
protected StudentFilePolicy getStudentFilePolicy(Path projectPath) {
86+
return new RStudentFilePolicy(projectPath);
87+
}
88+
89+
@Override
90+
public String getPluginName() {
91+
return "r";
92+
}
93+
94+
@Override
95+
public Optional<ExerciseDesc> scanExercise(Path path, String exerciseName) {
96+
ProcessRunner runner = new ProcessRunner(this.getAvailablePointsCommand(), path);
97+
98+
try {
99+
runner.call();
100+
} catch (Exception e) {
101+
log.error(CANNOT_SCAN_EXERCISE_MESSAGE, e);
102+
return Optional.absent();
103+
}
104+
105+
try {
106+
ImmutableList<TestDesc> testDescs = new RExerciseDescParser(path).parse();
107+
return Optional.of(new ExerciseDesc(exerciseName, testDescs));
108+
} catch (IOException e) {
109+
log.error(CANNOT_PARSE_EXERCISE_DESCRIPTION_MESSAGE, e);
110+
}
111+
112+
return Optional.absent();
113+
}
114+
115+
@Override
116+
public RunResult runTests(Path path) {
117+
ProcessRunner runner = new ProcessRunner(getTestCommand(), path);
118+
119+
try {
120+
runner.call();
121+
} catch (Exception e) {
122+
log.error(CANNOT_RUN_TESTS_MESSAGE, e);
123+
return getGenericErrorRunResult(e);
124+
}
125+
126+
try {
127+
return new RTestResultParser(path).parse();
128+
} catch (IOException e) {
129+
log.error(CANNOT_PARSE_TEST_RESULTS_MESSAGE, e);
130+
return getGenericErrorRunResult(e);
131+
}
132+
}
133+
134+
private RunResult getGenericErrorRunResult(Throwable exception) {
135+
Map<String, byte[]> logMap = new HashMap<>();
136+
byte[] stackTraceAsByteArray = ExceptionUtils.getStackTrace(exception).getBytes();
137+
logMap.put(SpecialLogs.GENERIC_ERROR_MESSAGE, stackTraceAsByteArray);
138+
139+
ImmutableMap<String, byte[]> logs = ImmutableMap.copyOf(logMap);
140+
141+
return new RunResult(RunResult.Status.GENERIC_ERROR,
142+
ImmutableList.copyOf(new ArrayList<TestResult>()), logs);
143+
}
144+
145+
@Override
146+
public ValidationResult checkCodeStyle(Path path, Locale messageLocale) {
147+
return new ValidationResult() {
148+
@Override
149+
public Strategy getStrategy() {
150+
return Strategy.DISABLED;
151+
}
152+
153+
@Override
154+
public Map<File, List<ValidationError>> getValidationErrors() {
155+
return Maps.newHashMap();
156+
}
157+
};
158+
}
159+
160+
public String[] getTestCommand() {
161+
String[] command = new String[]{"Rscript"};
162+
String[] args;
163+
if (SystemUtils.IS_OS_WINDOWS) {
164+
args = new String[]{"-e", "\"library('tmcRtestrunner');run_tests()\""};
165+
} else {
166+
args = new String[]{"-e", "library(tmcRtestrunner);run_tests()"};
167+
}
168+
return ArrayUtils.addAll(command, args);
169+
}
170+
171+
public String[] getAvailablePointsCommand() {
172+
String[] command = new String[]{"Rscript"};
173+
String[] args;
174+
if (SystemUtils.IS_OS_WINDOWS) {
175+
args = new String[]{"-e", "\"library('tmcRtestrunner');run_available_points()\""};
176+
} else {
177+
args = new String[]{"-e", "library(tmcRtestrunner);run_available_points()"};
178+
}
179+
return ArrayUtils.addAll(command, args);
180+
}
181+
182+
/**
183+
* No operation for now. To be possibly implemented later: remove .Rdata, .Rhistory etc
184+
*/
185+
@Override
186+
public void clean(Path path) {
187+
}
188+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package fi.helsinki.cs.tmc.langs.r;
2+
3+
import fi.helsinki.cs.tmc.langs.io.ConfigurableStudentFilePolicy;
4+
5+
import java.nio.file.Path;
6+
import java.nio.file.Paths;
7+
8+
public class RStudentFilePolicy extends ConfigurableStudentFilePolicy {
9+
10+
public RStudentFilePolicy(Path configFileParent) {
11+
super(configFileParent);
12+
}
13+
14+
/**
15+
* Returns {@code True} for all files in the <tt>projectRoot/R/</tt> directory and other
16+
* files required for building the project.
17+
*
18+
* <p>Will NOT return {@code True} for any test files. If test file modification are part
19+
* of the exercise, those test files are whitelisted as <tt>ExtraStudentFiles</tt> and the
20+
* decision to include them is made by {@link ConfigurableStudentFilePolicy}.
21+
*/
22+
@Override
23+
public boolean isStudentSourceFile(Path path, Path projectRootPath) {
24+
return path.startsWith(Paths.get("R"));
25+
}
26+
}

0 commit comments

Comments
 (0)