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

Refactor paths to dta-tests-assignments

No related merge requests found
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;
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
@RequestMapping("/v1/task/*")
@MultipartConfig
public class TaskUpload {
private static final Logger LOG = LogManager.getLogger(TaskUpload.class);
private final RepoUtil repoUtil;
private final Path testTmpPath;
private final ExecuteTestUtil executeTestUtil;
private final Tika tika;
public TaskUpload(
Environment env,
RepoUtil repoUtil,
ExecuteTestUtil executeTestUtil
) {
public TaskUpload(Environment env, RepoUtil repoUtil, ExecuteTestUtil executeTestUtil) {
this.repoUtil = repoUtil;
this.executeTestUtil = executeTestUtil;
// set path of temporary directory on host and inside our container
this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir"));
this.tika = new Tika();
}
@RequestMapping(method = RequestMethod.POST)
public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef,
@RequestParam("assignmentId") String assignmentId
) throws IOException, InterruptedException {
LOG.info("submission for testing received");
@RequestParam("assignmentId") String assignmentId)
throws IOException, InterruptedException {
LOG.info("Submission for testing received");
Path workDirectory = createWorkDirectory();
Path srcPath = defineSourcePath(workDirectory);
String mimeType = tika.detect(taskFileRef.getInputStream());
processUploadedFile(taskFileRef, srcPath, mimeType);
ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
if (isPlainTextFile(mimeType)) {
processTicketingInformation(taskFileRef, resultSummary);
}
LOG.debug("creating new temporary directory");
Path workDirectory = Files.createTempDirectory(testTmpPath, "dta");
LOG.debug(String.format("working dir for test is: %s", workDirectory.toAbsolutePath().toString()));
LOG.info("Submission tested successfully");
return resultSummary;
}
// define paths for the test, the submission and where the result is to be expected afterwards
Path srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src");
LOG.debug(String.format("Source path defined as: %s", srcPath.toAbsolutePath().toString()));
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;
}
String mimeInfo = new Tika().detect(taskFileRef.getInputStream());
switch (mimeInfo) {
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":
LOG.debug("textfile uploaded, searching for dta config");
// 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");
}
}
else {
subDir=config.group(4);
}
LOG.debug("calling repo clone");
repoUtil.cloneRepository(config, srcPath.toAbsolutePath().toString(), subDir);
handlePlainTextFile(taskFileRef, srcPath);
break;
case "application/zip":
LOG.debug("zip archive uploaded, extracting content as student submission");
ArchiveUtil.extractProjectFromZip(taskFileRef.getInputStream(), srcPath.toAbsolutePath());
handleZipFile(taskFileRef, srcPath);
break;
default:
String msg = String.format("couldn't process uploaded file with mime type %s", mimeInfo);
LOG.error(msg);
throw new RuntimeException(msg);
handleUnsupportedFileType(mimeType);
}
}
// run test
LOG.debug("calling test execution");
ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory);
private void handlePlainTextFile(MultipartFile taskFileRef, Path srcPath) throws IOException {
LOG.debug("Text file uploaded, searching for DTA config");
Matcher config = findRepositoryConfig(taskFileRef);
String subDir = config != null ? config.group(4) : "";
LOG.debug("Cloning repository");
repoUtil.cloneRepository(config, srcPath.toAbsolutePath().toString(), subDir);
}
if (mimeInfo.equals("text/plain")) {
LOG.info("check for provided Ticketsystem information");
UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary);
private Matcher findRepositoryConfig(MultipartFile taskFileRef) throws IOException {
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");
}
}
taskFileRef.getInputStream().close();
return config;
}
LOG.info("submission tested successfully");
return resultSummary;
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;
import com.fasterxml.jackson.core.exc.StreamReadException;
import com.fasterxml.jackson.databind.DatabindException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dockerjava.api.model.Bind;
import com.github.dockerjava.api.model.Volume;
import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
......@@ -33,59 +34,59 @@ public class ExecuteTestUtil {
private static final Logger LOG = LogManager.getLogger(ExecuteTestUtil.class);
private final DockerUtil dockerUtil;
private final String assignmentBasePath;
private final String containerTestDir;
private final String basePath;
private final String containerBasePath;
public ExecuteTestUtil(
Environment env,
DockerUtil dockerUtil
) {
public ExecuteTestUtil(Environment env, DockerUtil dockerUtil) {
this.dockerUtil = dockerUtil;
// set base path for assignments to be stored
Path p = Paths.get(
env.getProperty("data.dir"), ///data
env.getProperty("data.dir.test.folder.name")); //UnitTests
this.assignmentBasePath = p.toAbsolutePath().toString();
this.containerTestDir = env.getProperty( "data.dir");
this.basePath = p.toAbsolutePath().toString();
this.containerBasePath = env.getProperty( "data.dir");
}
public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
// Define paths for the submission-specific directories
String containerTestDir = this.containerBasePath + workDirectory.getFileName(); // /dta-test-assignments/dta-submissionID
Path testPath = Paths.get(containerTestDir, "test");
Path srcPath = Paths.get(containerTestDir, "src");
Path resultPath = Paths.get(containerTestDir, "result");
// Define paths for the test, the submission, and where the result is expected afterwards
String containerTestDir = this.containerTestDir;
Path resultPath = Paths.get(containerTestDir, "result");
Path testPath = Paths.get(containerTestDir, "test");
Path srcPath = Paths.get(containerTestDir, "src");
// Ensure directories exist
Files.createDirectories(testPath);
Files.createDirectories(srcPath);
Files.createDirectories(resultPath);
// Clone stored test to testPath
LOG.debug("Copying pre-downloaded unit test repo");
FileUtil.copyFolder(
Paths.get(assignmentBasePath, assignmentId),
testPath);
FileUtil.copyFolder(Paths.get(basePath, assignmentId), testPath);
LOG.debug("Copying exercise manifest");
Files.copy(Paths.get(
assignmentBasePath, assignmentId + "_checkout",
CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME),
Paths.get(
testPath.toString(),
CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME));
Files.copy(
Paths.get(basePath, assignmentId + "_checkout", CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME),
testPath.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME)
);
LOG.debug("Copying test config");
Files.copy(
Paths.get(assignmentBasePath, assignmentId + ".txt"),
Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt"));
Files.createDirectory(resultPath);
Paths.get(basePath, assignmentId + ".txt"),
Paths.get(containerTestDir, "config.txt")
);
LOG.info("Reading test config");
Matcher config = RegexUtil.extractConfig(
new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX));
String image = "";
new FileInputStream(Paths.get(containerTestDir, "config.txt").toFile()),
Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)
);
String image;
if (config == null) {
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) {
throw new RuntimeException("Couldn't find repo config for unit test image extraction");
}
......@@ -94,35 +95,33 @@ public class ExecuteTestUtil {
image = config.group(5);
}
// Start the test-container with professor-given image and volume mounts for test, submission, and result
dockerUtil.runContainerWithVolumes(
// Start the test-container with professor-given image and submission-specific volume mounts
dockerUtil.runContainerWithBinds(
image,
new Volume("test_volume: /data/test"),
new Volume("src_volume: /data/src"),
new Volume("result_volume: /data/result")
new Bind(testPath.toString(), new Volume("/data/test")),
new Bind(srcPath.toString(), new Volume("/data/src")),
new Bind(resultPath.toString(), new Volume("/data/result"))
);
ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPath);
return resultSummary;
return generateResult(assignmentId, resultPath, testPath);
}
private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPath)
throws IOException, StreamReadException, DatabindException, MalformedURLException {
// 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
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");
}
LOG.debug("Parse results JSON");
LOG.debug("Parsing results JSON");
ObjectMapper objectMapper = new ObjectMapper();
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...");
List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPath, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME);
LOG.debug(String.format(
......@@ -150,46 +149,48 @@ public class ExecuteTestUtil {
}
return resultSummary;
}
/*
* exercise recommendation part
*/
public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
throws FileNotFoundException {
public List<Recommendation> recommendNextExercises(String assignmentId, Path testPath, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
throws FileNotFoundException {
// fetch repo url from original test upload
Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
File file = Paths.get(assignmentBasePath, assignmentId + ".txt").toFile();
FileInputStream configFileStream = new FileInputStream(file);
Matcher config = RegexUtil.extractConfig(configFileStream, pattern);
String testRepoURL = config.group(1);
Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
File configFile = Paths.get(basePath, assignmentId + ".txt").toFile();
Matcher config = RegexUtil.extractConfig(new FileInputStream(configFile), pattern);
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;
float currentDifficulty=0.0f;
int currentTopicIndex = 0;
float currentDifficulty = 0.0f;
//build course topic order
Map<String, Integer> topicOrder = new HashMap<>();
int order = 1;
for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
if (!topicOrder.containsKey(e.exerciseTopicName)) {
topicOrder.put(e.exerciseTopicName, order++);
}
if (e.exerciseURL.equals(testRepoURL)) {
currentTopicIndex = order;
currentDifficulty = e.difficulty;
}
}
Map<String, Integer> topicOrder = new HashMap<>();
int order = 1;
for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
if (!topicOrder.containsKey(e.exerciseTopicName)) {
topicOrder.put(e.exerciseTopicName, order++);
}
if (e.exerciseURL.equals(testRepoURL)) {
currentTopicIndex = order;
currentDifficulty = e.difficulty;
}
}
//filter exercises according to success
float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL,
currentDifficulty, unsuccessful, resultSummary);
//compute recommendations
List<Recommendation> recommendedExercises = new ArrayList<>();
for (ExerciseCompetencyProfile exerciseProfile : filteredExercises) {
for (ExerciseCompetencyProfile exerciseProfile : filteredExercises) {
Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName,
exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty));
recommendedExercises.add(recommendation);
......@@ -199,7 +200,7 @@ public class ExecuteTestUtil {
recommendedExercises.stream().sorted(Recommendation.COMPARE_BY_SCORE).collect(Collectors.toList());
return recommendedExercises;
}
}
public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> exerciseCompetencyProfiles,
Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful,
......@@ -208,34 +209,34 @@ public class ExecuteTestUtil {
//option for later: include next topic if fullsuccess and current difficulty == max difficulty
List<ExerciseCompetencyProfile> filteredExercises = exerciseCompetencyProfiles.stream()
.filter(testProfile -> topicOrder.get(testProfile.exerciseTopicName) <= currentTopicIndex)
.collect(Collectors.toList());
.collect(Collectors.toList());
//filter by difficulty according to success
if (isFullSuccess(unsuccessful)) {
if (isFullSuccess(unsuccessful)) {
filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList());
} else {
} else {
filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty <= currentDifficulty).collect(Collectors.toList());
}
}
return filteredExercises;
}
public static boolean isFullSuccess(float[] unsuccessful) {
for (float value : unsuccessful) {
if (value != 0.0f) {
return false;
}
}
return true;
}
}
public static boolean isFullSuccess(float[] unsuccessful) {
for (float value : unsuccessful) {
if (value != 0.0f) {
return false;
}
}
return true;
}
public static float calculateScore(ExerciseCompetencyProfile exerciseProfile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) {
//ensure factor 1 for full success not to blank out score, thus offset the base
float score = 1.0f;
float score = 1.0f;
//competency profile difference to not fully achieved competencies component
for (int i = 0; i < exerciseProfile.competencyAssessments.length-1; i++) {
score += exerciseProfile.competencyAssessments[i] * unsuccessful[i];
}
}
//difficulty component
score = score * (exerciseProfile.difficulty*(0.5f+Math.abs(currentDifficulty-exerciseProfile.difficulty)));
......@@ -245,5 +246,5 @@ public class ExecuteTestUtil {
score = Math.round(score * 10.0f) / 10.0f;
return score;
}
}
\ No newline at end of file
}
}
......@@ -9,7 +9,7 @@ spring.http.multipart.max-file-size=5Mb
###############################################
# Holds the uploaded Zip-Files
tests.tmp.dir=/tmp/dta-tests
tests.tmp.dir=/dta-tests-assignments
host.tests.tmp.dir=${tests.tmp.dir}
data.dir=/data
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