Verified Commit c9e59167 authored by Lukas Wiest's avatar Lukas Wiest 🚂
Browse files

Merge branch 'feat-allow-direct-submissions'

parents 657b50aa 09926fdc
......@@ -74,6 +74,13 @@
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<!-- check if received multipart file is text or zip file -->
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>1.25</version>
</dependency>
</dependencies>
<build>
......
package de.hftstuttgart.rest.v1.task;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume;
import de.hftstuttgart.models.LegacyMoodleResult;
import de.hftstuttgart.models.ModocotResultSummary;
import de.hftstuttgart.rest.v1.unittest.UnitTestUpload;
import de.hftstuttgart.utils.DockerUtil;
import de.hftstuttgart.utils.FileUtil;
import de.hftstuttgart.utils.JGitUtil;
import de.hftstuttgart.utils.UnifiedTicketingUtil;
import de.hftstuttgart.utils.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tika.Tika;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
......@@ -25,7 +19,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Rest controller for everything related to the TASK files
......@@ -37,28 +30,18 @@ public class TaskUpload {
private static final Logger LOG = LogManager.getLogger(TaskUpload.class);
private final JGitUtil jGitUtil;
private final DockerUtil dockerUtil;
private final String assignmentBasePath;
private final Path testTmpPathHost;
private final Path testTmpPathModocot;
private final ExecuteTestUtil executeTestUtil;
public TaskUpload(
Environment env,
JGitUtil jGitUtil,
DockerUtil dockerUtil
ExecuteTestUtil executeTestUtil
) {
this.jGitUtil = jGitUtil;
this.dockerUtil = dockerUtil;
// set base path for assignments to be stored
Path p = Paths.get(
env.getProperty("modocot.dir"),
env.getProperty("modocot.dir.test.folder.name"));
this.assignmentBasePath = p.toAbsolutePath().toString();
this.executeTestUtil = executeTestUtil;
// set path of temporary directory on host and inside our container
this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
this.testTmpPathModocot = Paths.get(env.getProperty("modocot.tests.tmp.dir"));
}
......@@ -73,137 +56,42 @@ public class TaskUpload {
LOG.debug(String.format("working dir for test is: %s", workDirectory.toAbsolutePath().toString()));
// define paths for the test, the submission and where the result is to be expected afterwards
Path testPath = Paths.get(workDirectory.toAbsolutePath().toString(), "test");
Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src");
Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "result");
// clone stored test to tmpdir
LOG.debug("copying pre-downloaded unitttest repo");
FileUtil.copyFolder(
Paths.get(
assignmentBasePath,
assignmentId
),
testPath
);
LOG.debug("copy test config");
Files.copy(
Paths.get(
assignmentBasePath,
assignmentId + ".txt"
),
Paths.get(
workDirectory.toAbsolutePath().toString(),
"config.txt"
)
);
// clone student code to tmpdir
Pattern pattern = Pattern.compile(UnitTestUpload.modocotDueConfigRegex);
Matcher config = null;
LOG.debug("reading task file");
// open received file in a try-with
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
taskFileRef.getInputStream()))) {
String line;
// as long as we haven't found a configuration and have lines left, search
while (config == null && (line = br.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
LOG.debug(String.format("found modocot line: %s", line));
config = matcher;
}
}
} catch (IOException e) {
LOG.error("Error while reading repo config", e);
}
finally {
if (config == null) {
throw new RuntimeException("couldn't find repo config for unittest clone");
}
}
LOG.debug("calling repo clone");
jGitUtil.cloneRepository(config, srcPath.toAbsolutePath().toString());
Files.createDirectory(resultPath);
LOG.info("starting unittest");
pattern = Pattern.compile(UnitTestUpload.modocotTestConfigRegex);
config = null;
LOG.debug("reading test config");
// open test config and read it in again, important to know with which docker image the test should be run
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile())))) {
String line;
while (config == null && (line = br.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
LOG.debug(String.format("found modocot line: %s", line));
config = matcher;
}
}
} catch (IOException e) {
LOG.error("Error while reading test config", e);
}
finally {
if (config == null) {
throw new RuntimeException("couldn't find test config for unittest");
}
}
// define the paths to mount as Binds from Host to the test-container
Path testPathHost = Paths.get(
testTmpPathHost.toAbsolutePath().toString(),
workDirectory.getName(workDirectory.getNameCount()-1).toString(),
testPath.getName(testPath.getNameCount()-1).toString()
);
Path srcPathHost = Paths.get(
testTmpPathHost.toAbsolutePath().toString(),
workDirectory.getName(workDirectory.getNameCount()-1).toString(),
srcPath.getName(srcPath.getNameCount()-1).toString()
);
Path resultPathHost = Paths.get(
testTmpPathHost.toAbsolutePath().toString(),
workDirectory.getName(workDirectory.getNameCount()-1).toString(),
resultPath.getName(resultPath.getNameCount()-1).toString()
);
// start test-container with professor given image and bind mounts for test, submission and result
dockerUtil.runContainer(
config.group(4),
new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/modocot/test")),
new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/modocot/src")),
new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/modocot/result"))
);
// define expected result file
File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
// check if result file is there
if (!resultFile.exists() || !resultFile.isFile()) {
LOG.error(String.format("couln't find result file in %s", resultFile.getAbsolutePath()));
throw new RuntimeException("no resultfile found");
String mimeInfo = new Tika().detect(taskFileRef.getInputStream());
switch (mimeInfo) {
case "text/plain":
LOG.debug("textfile uploaded, searching for modocot config");
// find modocot URI in config file
Matcher config = RegexUtil.findModocotStudentConfig(taskFileRef.getInputStream());
LOG.debug("calling repo clone");
jGitUtil.cloneRepository(config, srcPath.toAbsolutePath().toString());
break;
case "application/zip":
LOG.debug("zip archive uploaded, extracting content as student submission");
ArchiveUtil.extractProjectFromZip(taskFileRef.getInputStream(), srcPath.toAbsolutePath());
break;
default:
String msg = String.format("couldn't process uploaded file with mime type %s", mimeInfo);
LOG.error(msg);
throw new RuntimeException(msg);
}
LOG.debug("parse results json");
ObjectMapper objectMapper = new ObjectMapper();
ModocotResultSummary resultSummary = objectMapper.readValue(
resultFile.toURI().toURL(),
ModocotResultSummary.class);
// run test
LOG.debug("calling test execution");
ModocotResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
// convert to moddle plugin readable format and return to moodle
LOG.debug("convert to moodle understandable format");
LegacyMoodleResult moodleResult = LegacyMoodleResult.convertToModdleResult(resultSummary);
LOG.info("check for provided Ticketsystem information");
UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
if (mimeInfo.equals("text/plain")) {
LOG.info("check for provided Ticketsystem information");
UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
}
LOG.info("submission tested successfully");
return moodleResult;
......
package de.hftstuttgart.utils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.*;
import java.nio.file.Path;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ArchiveUtil
{
private static final Logger LOG = LogManager.getLogger(ArchiveUtil.class);
public static void extractProjectFromZip (InputStream is, Path outDir) throws IOException
{
LOG.info(String.format("starting archive extraction to %s", outDir.toAbsolutePath().toString()));
try (ZipInputStream stream = new ZipInputStream(is)) {
ZipEntry entry;
while ((entry = stream.getNextEntry()) != null) {
File file = outDir.resolve(entry.getName()).toFile();
LOG.debug(String.format("processing entry %s", file.getAbsolutePath()));
if (!entry.isDirectory()) {
LOG.debug("creating parent folders");
file.getParentFile().mkdirs();
LOG.debug("creating new file");
file.createNewFile();
byte[] buffer = new byte[2048];
try (FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) {
LOG.debug("writing content from zip entry to file");
int len;
while ((len = stream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
}
}
}
}
}
}
package de.hftstuttgart.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume;
import de.hftstuttgart.models.ModocotResultSummary;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
@Component
public class ExecuteTestUtil {
private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class);
private final JGitUtil jGitUtil;
private final DockerUtil dockerUtil;
private final String assignmentBasePath;
private final Path testTmpPathHost;
private final Path testTmpPathModocot;
public ExecuteTestUtil(
Environment env,
JGitUtil jGitUtil,
DockerUtil dockerUtil
) {
this.jGitUtil = jGitUtil;
this.dockerUtil = dockerUtil;
// set base path for assignments to be stored
Path p = Paths.get(
env.getProperty("modocot.dir"),
env.getProperty("modocot.dir.test.folder.name"));
this.assignmentBasePath = p.toAbsolutePath().toString();
// set path of temporary directory on host and inside our container
this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
this.testTmpPathModocot = Paths.get(env.getProperty("modocot.tests.tmp.dir"));
}
public ModocotResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
// define paths for the test, the submission and where the result is to be expected afterwards
Path testPath = Paths.get(workDirectory.toAbsolutePath().toString(), "test");
Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src");
Path resultPath = Paths.get(workDirectory.toAbsolutePath().toString(), "result");
// clone stored test to tmpdir
LOG.debug("copying pre-downloaded unitttest repo");
FileUtil.copyFolder(
Paths.get(
assignmentBasePath,
assignmentId
),
testPath
);
LOG.debug("copy test config");
Files.copy(
Paths.get(
assignmentBasePath,
assignmentId + ".txt"
),
Paths.get(
workDirectory.toAbsolutePath().toString(),
"config.txt"
)
);
Files.createDirectory(resultPath);
LOG.info("reading test config");
Matcher config = RegexUtil.findModocotProfessorConfig(
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()));
// define the paths to mount as Binds from Host to the test-container
Path testPathHost = Paths.get(
testTmpPathHost.toAbsolutePath().toString(),
workDirectory.getName(workDirectory.getNameCount()-1).toString(),
testPath.getName(testPath.getNameCount()-1).toString()
);
Path srcPathHost = Paths.get(
testTmpPathHost.toAbsolutePath().toString(),
workDirectory.getName(workDirectory.getNameCount()-1).toString(),
srcPath.getName(srcPath.getNameCount()-1).toString()
);
Path resultPathHost = Paths.get(
testTmpPathHost.toAbsolutePath().toString(),
workDirectory.getName(workDirectory.getNameCount()-1).toString(),
resultPath.getName(resultPath.getNameCount()-1).toString()
);
// start test-container with professor given image and bind mounts for test, submission and result
dockerUtil.runContainer(
config.group(4),
new Bind(testPathHost.toAbsolutePath().toString(), new Volume("/modocot/test")),
new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/modocot/src")),
new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/modocot/result"))
);
// define expected result file
File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
// check if result file is there
if (!resultFile.exists() || !resultFile.isFile()) {
LOG.error(String.format("couln't find result file in %s", resultFile.getAbsolutePath()));
throw new RuntimeException("no resultfile found");
}
LOG.debug("parse results json");
ObjectMapper objectMapper = new ObjectMapper();
ModocotResultSummary resultSummary = objectMapper.readValue(
resultFile.toURI().toURL(),
ModocotResultSummary.class);
return resultSummary;
}
}
package de.hftstuttgart.utils;
import de.hftstuttgart.rest.v1.unittest.UnitTestUpload;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexUtil {
public enum ConfigType {
PROFESSOR,
STUDENT,
}
private static final Logger LOG = LogManager.getLogger(RegexUtil.class);
public static Matcher findModocotStudentConfig(InputStream is) {
return findModoctConfig(is, ConfigType.STUDENT);
}
public static Matcher findModocotProfessorConfig(InputStream is) {
return findModoctConfig(is, ConfigType.PROFESSOR);
}
public static Matcher findModoctConfig(InputStream is, ConfigType configType) {
Pattern pattern;
switch (configType) {
case PROFESSOR:
pattern = Pattern.compile(UnitTestUpload.modocotTestConfigRegex);
break;
case STUDENT:
pattern = Pattern.compile(UnitTestUpload.modocotDueConfigRegex);
break;
default:
String msg = String.format("unknown config type: %s", configType.name());
LOG.error(msg);
throw new RuntimeException(msg);
}
Matcher config = null;
LOG.debug("reading config file");
// open received file in a try-with
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
is))) {
String line;
// as long as we haven't found a configuration and have lines left, search
while (config == null && (line = br.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.matches()) {
LOG.debug(String.format("found modocot line: %s", line));
config = matcher;
}
}
} catch (IOException e) {
LOG.error("Error while reading repo config", e);
}
finally {
if (config == null) {
throw new RuntimeException("couldn't find repo config for clone");
}
}
return config;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment