Commit 92f938c9 authored by mamunozgil's avatar mamunozgil
Browse files

Refactor paths to dta-tests-assignments

parent 6c7bc49f
Pipeline #10776 passed with stage
in 18 seconds
Showing with 169 additions and 139 deletions
+169 -139
...@@ -23,88 +23,117 @@ import java.util.regex.Pattern; ...@@ -23,88 +23,117 @@ import java.util.regex.Pattern;
import jakarta.servlet.annotation.MultipartConfig; import jakarta.servlet.annotation.MultipartConfig;
/** /**
* Rest controller for everything related to the TASK files * Rest controller for handling code assignment file uploads and testing.
*/ */
@RestController @RestController
@RequestMapping("/v1/task/*") @RequestMapping("/v1/task/*")
@MultipartConfig @MultipartConfig
public class TaskUpload { public class TaskUpload {
private static final Logger LOG = LogManager.getLogger(TaskUpload.class); private static final Logger LOG = LogManager.getLogger(TaskUpload.class);
private final RepoUtil repoUtil; private final RepoUtil repoUtil;
private final Path testTmpPath; private final Path testTmpPath;
private final ExecuteTestUtil executeTestUtil; private final ExecuteTestUtil executeTestUtil;
private final Tika tika;
public TaskUpload( public TaskUpload(Environment env, RepoUtil repoUtil, ExecuteTestUtil executeTestUtil) {
Environment env,
RepoUtil repoUtil,
ExecuteTestUtil executeTestUtil
) {
this.repoUtil = repoUtil; this.repoUtil = repoUtil;
this.executeTestUtil = executeTestUtil; this.executeTestUtil = executeTestUtil;
// set path of temporary directory on host and inside our container
this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir")); this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir"));
this.tika = new Tika();
} }
@RequestMapping(method = RequestMethod.POST) @RequestMapping(method = RequestMethod.POST)
public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef, public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef,
@RequestParam("assignmentId") String assignmentId @RequestParam("assignmentId") String assignmentId)
) throws IOException, InterruptedException { throws IOException, InterruptedException {
LOG.info("submission for testing received"); LOG.info("Submission for testing received");
LOG.debug("creating new temporary directory"); Path workDirectory = createWorkDirectory();
Path workDirectory = Files.createTempDirectory(testTmpPath, "dta"); Path srcPath = defineSourcePath(workDirectory);
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 String mimeType = tika.detect(taskFileRef.getInputStream());
Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src"); processUploadedFile(taskFileRef, srcPath, mimeType);
LOG.debug(String.format("Source path defined as: %s", srcPath.toAbsolutePath().toString()));
String mimeInfo = new Tika().detect(taskFileRef.getInputStream()); ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
switch (mimeInfo) {
case "text/plain": if (isPlainTextFile(mimeType)) {
LOG.debug("textfile uploaded, searching for dta config"); processTicketingInformation(taskFileRef, resultSummary);
// find URI in config file
String subDir="";
Matcher config = RegexUtil.extractConfig(taskFileRef.getInputStream(), Pattern.compile(RegexUtil.DTA_SUBMISSIONCONFIGREGEX));
if(config==null) {
config = RegexUtil.extractConfig(taskFileRef.getInputStream(), Pattern.compile(RegexUtil.SUBMISSIONCONFIGREGEX));
if(config==null)
{
throw new RuntimeException("couldn't find repo config for student submission clone");
} }
LOG.info("Submission tested successfully");
return resultSummary;
} }
else {
subDir=config.group(4); private Path createWorkDirectory() throws IOException {
LOG.debug("Creating new temporary directory");
Path workDirectory = Files.createTempDirectory(testTmpPath, "dta-submission");
LOG.debug("Working directory for test: {}", workDirectory.toAbsolutePath());
return workDirectory;
} }
LOG.debug("calling repo clone");
repoUtil.cloneRepository(config, srcPath.toAbsolutePath().toString(), subDir); private Path defineSourcePath(Path workDirectory) {
Path srcPath = workDirectory.resolve("src");
LOG.debug("Source path defined as: {}", srcPath.toAbsolutePath());
return srcPath;
}
private void processUploadedFile(MultipartFile taskFileRef, Path srcPath, String mimeType) throws IOException {
switch (mimeType) {
case "text/plain":
handlePlainTextFile(taskFileRef, srcPath);
break; break;
case "application/zip": case "application/zip":
LOG.debug("zip archive uploaded, extracting content as student submission"); handleZipFile(taskFileRef, srcPath);
ArchiveUtil.extractProjectFromZip(taskFileRef.getInputStream(), srcPath.toAbsolutePath());
break; break;
default: default:
String msg = String.format("couldn't process uploaded file with mime type %s", mimeInfo); handleUnsupportedFileType(mimeType);
LOG.error(msg); }
throw new RuntimeException(msg);
} }
// run test private void handlePlainTextFile(MultipartFile taskFileRef, Path srcPath) throws IOException {
LOG.debug("calling test execution"); LOG.debug("Text file uploaded, searching for DTA config");
ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
if (mimeInfo.equals("text/plain")) { Matcher config = findRepositoryConfig(taskFileRef);
LOG.info("check for provided Ticketsystem information"); String subDir = config != null ? config.group(4) : "";
UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
LOG.debug("Cloning repository");
repoUtil.cloneRepository(config, srcPath.toAbsolutePath().toString(), subDir);
} }
taskFileRef.getInputStream().close(); private Matcher findRepositoryConfig(MultipartFile taskFileRef) throws IOException {
Matcher config = RegexUtil.extractConfig(taskFileRef.getInputStream(), Pattern.compile(RegexUtil.DTA_SUBMISSIONCONFIGREGEX));
LOG.info("submission tested successfully"); if (config == null) {
return resultSummary; config = RegexUtil.extractConfig(taskFileRef.getInputStream(), Pattern.compile(RegexUtil.SUBMISSIONCONFIGREGEX));
if (config == null) {
throw new RuntimeException("Couldn't find repo config for student submission clone");
}
}
return config;
}
private void handleZipFile(MultipartFile taskFileRef, Path srcPath) throws IOException {
LOG.debug("ZIP archive uploaded, extracting content");
ArchiveUtil.extractProjectFromZip(taskFileRef.getInputStream(), srcPath.toAbsolutePath());
}
private void handleUnsupportedFileType(String mimeType) {
String msg = String.format("Couldn't process uploaded file with MIME type: %s", mimeType);
LOG.error(msg);
throw new RuntimeException(msg);
}
private boolean isPlainTextFile(String mimeType) {
return "text/plain".equals(mimeType);
}
private void processTicketingInformation(MultipartFile taskFileRef, ResultSummary resultSummary) throws IOException {
LOG.info("Checking for provided Ticketing system information");
UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
} }
} }
...@@ -3,6 +3,7 @@ package de.hftstuttgart.dtabackend.utils; ...@@ -3,6 +3,7 @@ package de.hftstuttgart.dtabackend.utils;
import com.fasterxml.jackson.core.exc.StreamReadException; import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException; import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume; import com.github.dockerjava.api.model.Volume;
import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile; import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
...@@ -33,59 +34,59 @@ public class ExecuteTestUtil { ...@@ -33,59 +34,59 @@ public class ExecuteTestUtil {
private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class); private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class);
private final DockerUtil dockerUtil; private final DockerUtil dockerUtil;
private final String assignmentBasePath; private final String basePath;
private final String containerTestDir; private final String containerBasePath;
public ExecuteTestUtil( public ExecuteTestUtil(Environment env, DockerUtil dockerUtil) {
Environment env,
DockerUtil dockerUtil
) {
this.dockerUtil = dockerUtil; this.dockerUtil = dockerUtil;
// set base path for assignments to be stored // set base path for assignments to be stored
Path p = Paths.get( Path p = Paths.get(
env.getProperty("data.dir"), ///data env.getProperty("data.dir"), ///data
env.getProperty("data.dir.test.folder.name")); //UnitTests env.getProperty("data.dir.test.folder.name")); //UnitTests
this.assignmentBasePath = p.toAbsolutePath().toString(); this.basePath = p.toAbsolutePath().toString();
this.containerTestDir = env.getProperty( "data.dir"); this.containerBasePath = env.getProperty( "data.dir");
} }
public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException { public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
// Define paths for the submission-specific directories
// Define paths for the test, the submission, and where the result is expected afterwards String containerTestDir = this.containerBasePath + workDirectory.getFileName(); // /dta-test-assignments/dta-submissionID
String containerTestDir = this.containerTestDir;
Path resultPath = Paths.get(containerTestDir, "result");
Path testPath = Paths.get(containerTestDir, "test"); Path testPath = Paths.get(containerTestDir, "test");
Path srcPath = Paths.get(containerTestDir, "src"); Path srcPath = Paths.get(containerTestDir, "src");
Path resultPath = Paths.get(containerTestDir, "result");
// Ensure directories exist
Files.createDirectories(testPath);
Files.createDirectories(srcPath);
Files.createDirectories(resultPath);
// Clone stored test to testPath // Clone stored test to testPath
LOG.debug("Copying pre-downloaded unit test repo"); LOG.debug("Copying pre-downloaded unit test repo");
FileUtil.copyFolder( FileUtil.copyFolder(Paths.get(basePath, assignmentId), testPath);
Paths.get(assignmentBasePath, assignmentId),
testPath);
LOG.debug("Copying exercise manifest"); LOG.debug("Copying exercise manifest");
Files.copy(Paths.get( Files.copy(
assignmentBasePath, assignmentId + "_checkout", Paths.get(basePath, assignmentId + "_checkout", CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME),
CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME), testPath.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME)
Paths.get( );
testPath.toString(),
CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME));
LOG.debug("Copying test config"); LOG.debug("Copying test config");
Files.copy( Files.copy(
Paths.get(assignmentBasePath, assignmentId + ".txt"), Paths.get(basePath, assignmentId + ".txt"),
Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt")); Paths.get(containerTestDir, "config.txt")
);
Files.createDirectory(resultPath);
LOG.info("Reading test config"); LOG.info("Reading test config");
Matcher config = RegexUtil.extractConfig( Matcher config = RegexUtil.extractConfig(
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)); new FileInputStream(Paths.get(containerTestDir, "config.txt").toFile()),
String image = ""; Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)
);
String image;
if (config == null) { if (config == null) {
config = RegexUtil.extractConfig( config = RegexUtil.extractConfig(
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.TESTCONFIGREGEX)); new FileInputStream(Paths.get(containerTestDir, "config.txt").toFile()),
Pattern.compile(RegexUtil.TESTCONFIGREGEX)
);
if (config == null) { if (config == null) {
throw new RuntimeException("Couldn't find repo config for unit test image extraction"); throw new RuntimeException("Couldn't find repo config for unit test image extraction");
} }
...@@ -94,34 +95,32 @@ public class ExecuteTestUtil { ...@@ -94,34 +95,32 @@ public class ExecuteTestUtil {
image = config.group(5); image = config.group(5);
} }
// Start the test-container with professor-given image and volume mounts for test, submission, and result // Start the test-container with professor-given image and submission-specific volume mounts
dockerUtil.runContainerWithVolumes( dockerUtil.runContainerWithBinds(
image, image,
new Volume("test_volume: /data/test"), new Bind(testPath.toString(), new Volume("/data/test")),
new Volume("src_volume: /data/src"), new Bind(srcPath.toString(), new Volume("/data/src")),
new Volume("result_volume: /data/result") new Bind(resultPath.toString(), new Volume("/data/result"))
); );
ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPath); return generateResult(assignmentId, resultPath, testPath);
return resultSummary;
} }
private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPath) private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPath)
throws IOException, StreamReadException, DatabindException, MalformedURLException { throws IOException, StreamReadException, DatabindException, MalformedURLException {
// Define expected result file // Define expected result file
File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile(); File resultFile = resultPath.resolve("result.json").toFile();
// Check if result file is there // Check if result file is there
if (!resultFile.exists() || !resultFile.isFile()) { if (!resultFile.exists() || !resultFile.isFile()) {
LOG.error(String.format("Could not find result file in %s", resultFile.getAbsolutePath())); LOG.error("Could not find result file in {}", resultFile.getAbsolutePath());
throw new RuntimeException("No result file found"); throw new RuntimeException("No result file found");
} }
LOG.debug("Parse results JSON"); LOG.debug("Parsing results JSON");
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
ResultSummary resultSummary = objectMapper.readValue(resultFile.toURI().toURL(), ResultSummary.class); ResultSummary resultSummary = objectMapper.readValue(resultFile.toURI().toURL(), ResultSummary.class);
LOG.debug("Result JSON returned time " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results."); LOG.debug("Result JSON returned time {} with {} test results.", resultSummary.timestamp, resultSummary.results.size());
LOG.info("Checking for optional test competency profile information for pedagogical agent functionality..."); LOG.info("Checking for optional test competency profile information for pedagogical agent functionality...");
List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPath, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME); List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPath, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME);
...@@ -154,23 +153,25 @@ public class ExecuteTestUtil { ...@@ -154,23 +153,25 @@ public class ExecuteTestUtil {
/* /*
* exercise recommendation part * exercise recommendation part
*/ */
public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) public List<Recommendation> recommendNextExercises(String assignmentId, Path testPath, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
throws FileNotFoundException { throws FileNotFoundException {
// fetch repo url from original test upload // fetch repo url from original test upload
Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX); Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
File file = Paths.get(assignmentBasePath, assignmentId + ".txt").toFile(); File configFile = Paths.get(basePath, assignmentId + ".txt").toFile();
FileInputStream configFileStream = new FileInputStream(file); Matcher config = RegexUtil.extractConfig(new FileInputStream(configFile), pattern);
Matcher config = RegexUtil.extractConfig(configFileStream, pattern);
String testRepoURL = config.group(1); String testRepoURL = config.group(1);
List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME); List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(
testPath, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME
);
int currentTopicIndex=0; int currentTopicIndex = 0;
float currentDifficulty=0.0f; float currentDifficulty = 0.0f;
//build course topic order //build course topic order
Map<String, Integer> topicOrder = new HashMap<>(); Map<String, Integer> topicOrder = new HashMap<>();
int order = 1; int order = 1;
for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) { for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
if (!topicOrder.containsKey(e.exerciseTopicName)) { if (!topicOrder.containsKey(e.exerciseTopicName)) {
topicOrder.put(e.exerciseTopicName, order++); topicOrder.put(e.exerciseTopicName, order++);
......
...@@ -9,7 +9,7 @@ spring.http.multipart.max-file-size=5Mb ...@@ -9,7 +9,7 @@ spring.http.multipart.max-file-size=5Mb
############################################### ###############################################
# Holds the uploaded Zip-Files # Holds the uploaded Zip-Files
tests.tmp.dir=/tmp/dta-tests tests.tmp.dir=/dta-tests-assignments
host.tests.tmp.dir=${tests.tmp.dir} host.tests.tmp.dir=${tests.tmp.dir}
data.dir=/data data.dir=/data
data.dir.test.folder.name=UnitTests data.dir.test.folder.name=UnitTests
......
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