diff --git a/Dockerfile b/Dockerfile index bfee0545fe61a8152215e3df7c687a387551babd..f5ddbb7db145c4032613df15e0d3cf4b1f1a0f64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,48 +1,50 @@ -# base image to build a JRE -FROM amazoncorretto:17.0.3-alpine as corretto-jdk - -# required for strip-debug to work -RUN apk add --no-cache binutils - -# Build small JRE image -RUN $JAVA_HOME/bin/jlink \ - --verbose \ - --add-modules ALL-MODULE-PATH \ - --strip-debug \ - --no-man-pages \ - --no-header-files \ - --compress=2 \ - --output /customjre - -# main app image -FROM alpine:latest - -ENV JAVA_HOME=/jre -ENV PATH="${JAVA_HOME}/bin:${PATH}" -ENV SPRING_CONFIG_ADDITIONAL_LOCATION "file:/data/config/" - -# copy JRE from the base image -COPY --from=corretto-jdk /customjre $JAVA_HOME - -# Add app user -ARG AUSER=appuser -ARG AGID=137 -ENV USER=$AUSER -ENV GID=$AGID -ARG BUILD_NUMBER= -RUN addgroup -g $GID -S docker -RUN adduser --no-create-home -u 1000 -G docker -D $USER - -# Prepare environment. -# Create needed folders -RUN mkdir /data && \ - mkdir /data/config && \ - chown -R $USER /data - -VOLUME /data - -COPY --chown=1000:$GID target/dta-backend-$BUILD_NUMBER.jar app.jar - -USER 1000:$GID - -ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] +# base image to build a JRE +FROM amazoncorretto:17.0.3-alpine as corretto-jdk + +# required for strip-debug to work +RUN apk add --no-cache binutils + +# Build small JRE image +RUN $JAVA_HOME/bin/jlink \ + --verbose \ + --add-modules ALL-MODULE-PATH \ + --strip-debug \ + --no-man-pages \ + --no-header-files \ + --compress=2 \ + --output /customjre + +# main app image +FROM alpine:latest + +ENV JAVA_HOME=/jre +ENV PATH="${JAVA_HOME}/bin:${PATH}" +ENV SPRING_CONFIG_ADDITIONAL_LOCATION "file:/data/config/" + +# copy JRE from the base image +COPY --from=corretto-jdk /customjre $JAVA_HOME + +# Add app user +ARG AUSER=appuser +ARG AGID=137 +ENV USER=$AUSER +ENV GID=$AGID +ARG BUILD_NUMBER= + +# Create docker group identical to host +RUN addgroup -g $GID -S docker +RUN adduser --no-create-home -u 1000 -G docker -D $USER + +# Prepare environment. +# Create needed folders +RUN mkdir /data && \ + mkdir /data/config && \ + chown -R $USER /data + +VOLUME /data + +COPY --chown=1000:$GID target/dta-backend-$BUILD_NUMBER.jar app.jar + +USER 1000:$GID + +ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] diff --git a/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java index 4c4e520d8241e0082b272c0dc8e377da81be4d25..3e2151994167c7ab4a555aef970c2c8be88e54ae 100644 --- a/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java +++ b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/task/TaskUpload.java @@ -1,97 +1,97 @@ -package de.hftstuttgart.dtabackend.rest.v1.task; - -import de.hftstuttgart.dtabackend.utils.*; -import de.hftstuttgart.dtabackend.models.ResultSummary; - -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; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.regex.Matcher; -import jakarta.servlet.annotation.MultipartConfig; - -/** - * Rest controller for everything related to the TASK files - */ -@RestController -@RequestMapping("/v1/task") -@MultipartConfig -public class TaskUpload { - private static final Logger LOG = LogManager.getLogger(TaskUpload.class); - - private final JGitUtil jGitUtil; - private final Path testTmpPath; - private final ExecuteTestUtil executeTestUtil; - - public TaskUpload( - Environment env, - JGitUtil jGitUtil, - ExecuteTestUtil executeTestUtil - ) { - this.jGitUtil = jGitUtil; - this.executeTestUtil = executeTestUtil; - - // set path of temporary directory on host and inside our container - this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir")); - } - - @RequestMapping(method = RequestMethod.POST) - public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef, - @RequestParam("assignmentId") String assignmentId - ) throws IOException, InterruptedException { - LOG.info("submission for testing received"); - - LOG.debug("creating new temporary directory"); - Path workDirectory = Files.createTempDirectory(testTmpPath, "dtt"); - 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 srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src"); - - String mimeInfo = new Tika().detect(taskFileRef.getInputStream()); - switch (mimeInfo) { - case "text/plain": - LOG.debug("textfile uploaded, searching for dtt config"); - // find URI in config file - Matcher config = RegexUtil.findStudentConfig(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); - } - - // run test - LOG.debug("calling test execution"); - ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory); - - if (mimeInfo.equals("text/plain")) { - LOG.info("check for provided Ticketsystem information"); - UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary); - } - - taskFileRef.getInputStream().close(); - - LOG.info("submission tested successfully"); - return resultSummary; - } -} +package de.hftstuttgart.dtabackend.rest.v1.task; + +import de.hftstuttgart.dtabackend.utils.*; +import de.hftstuttgart.dtabackend.models.ResultSummary; + +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; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.regex.Matcher; +import jakarta.servlet.annotation.MultipartConfig; + +/** + * Rest controller for everything related to the TASK files + */ +@RestController +@RequestMapping("/v1/task/*") +@MultipartConfig +public class TaskUpload { + private static final Logger LOG = LogManager.getLogger(TaskUpload.class); + + private final JGitUtil jGitUtil; + private final Path testTmpPath; + private final ExecuteTestUtil executeTestUtil; + + public TaskUpload( + Environment env, + JGitUtil jGitUtil, + ExecuteTestUtil executeTestUtil + ) { + this.jGitUtil = jGitUtil; + this.executeTestUtil = executeTestUtil; + + // set path of temporary directory on host and inside our container + this.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir")); + } + + @RequestMapping(method = RequestMethod.POST) + public ResultSummary uploadAndTestFile(@RequestParam("taskFile") MultipartFile taskFileRef, + @RequestParam("assignmentId") String assignmentId + ) throws IOException, InterruptedException { + LOG.info("submission for testing received"); + + LOG.debug("creating new temporary directory"); + Path workDirectory = Files.createTempDirectory(testTmpPath, "dtt"); + 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 srcPath = Paths.get(workDirectory.toAbsolutePath().toString(), "src"); + + String mimeInfo = new Tika().detect(taskFileRef.getInputStream()); + switch (mimeInfo) { + case "text/plain": + LOG.debug("textfile uploaded, searching for dtt config"); + // find URI in config file + Matcher config = RegexUtil.findStudentConfig(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); + } + + // run test + LOG.debug("calling test execution"); + ResultSummary resultSummary = executeTestUtil.runTests(assignmentId, workDirectory); + + if (mimeInfo.equals("text/plain")) { + LOG.info("check for provided Ticketsystem information"); + UnifiedTicketingUtil.reportResults(taskFileRef.getInputStream(), resultSummary); + } + + taskFileRef.getInputStream().close(); + + LOG.info("submission tested successfully"); + return resultSummary; + } +} diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java index bf8b636936651e08a0522a825ee921a3dc5fd38e..40284d0d504e7e106175033d5046262f15ac501a 100644 --- a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java +++ b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java @@ -1,90 +1,98 @@ -package de.hftstuttgart.dtabackend.utils; - -import java.io.BufferedReader; -import java.io.FileReader; -import java.io.IOException; -import java.nio.file.Path; -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import de.hftstuttgart.dtabackend.models.Result; -import de.hftstuttgart.dtabackend.models.ResultSummary; -import de.hftstuttgart.dtabackend.models.TestCompetencyProfile; - -import java.io.FileNotFoundException; - -public class CompetencyAssessmentUtil { - private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class); - - public static String TEST_COMPETENCY_MANIFEST_FILE_NAME="competency-tests.mft"; - -/* public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException { - List<TestCompetencyProfile> testCompetencyProfiles=readTestCompetencyProfiles(Path.of(args[0]), args[1]); - sumTestCompetencyProfiles(testCompetencyProfiles); - ObjectMapper objectMapper = new ObjectMapper(); - ResultSummary resultSummary = objectMapper.readValue( - new File(Path.of(args[0]).toFile(), args[2]).toURI().toURL(), - ResultSummary.class); - sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary); - } -*/ - public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) { - float[] tcpTotalProfile=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS]; - for(TestCompetencyProfile currentProfile: testCompetencyProfiles) { - tcpTotalProfile=TestCompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments); - } - return tcpTotalProfile; - } - - public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) { - float[] sumSuccessful=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS]; - for(Result currentResult: resultSummary.results) { - if(currentResult.state==Result.State.SUCCESS.ordinal()) { - TestCompetencyProfile currentProfile=new TestCompetencyProfile(); - currentProfile.testPackageName=(currentResult.packageName!=null)?currentResult.packageName:""; - currentProfile.testClassName=(currentResult.className!=null)?currentResult.className:""; - currentProfile.testName=(currentResult.name!=null)?currentResult.name:""; - int testIndex=testCompetencyProfiles.indexOf(currentProfile); - if(testIndex!=-1) { - sumSuccessful=TestCompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments); - } - } - } - return sumSuccessful; - } - - public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path testPath, String fileName) { - List<TestCompetencyProfile> testCompetencyProfiles=new ArrayList<TestCompetencyProfile>(); - try { - BufferedReader testCompetencyManifest=new BufferedReader(new FileReader(new File(testPath.toFile(), fileName))); - String testEntry=testCompetencyManifest.readLine(); - while(testEntry!=null) - { - String[] testEntyComponents=testEntry.split(TestCompetencyProfile.COMPETENCY_SEPARATOR); - TestCompetencyProfile currentProfile=new TestCompetencyProfile(); - currentProfile.testPackageName=testEntyComponents[0]; - currentProfile.testClassName=testEntyComponents[1]; - currentProfile.testName=testEntyComponents[2]; - for(int competencyIndex=0; competencyIndex<TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) { - currentProfile.competencyAssessments[competencyIndex]=Float.valueOf(testEntyComponents[competencyIndex+3]); - } - testCompetencyProfiles.add(currentProfile); - testEntry=testCompetencyManifest.readLine(); - } - testCompetencyManifest.close(); - LOG.info("Added "+testCompetencyProfiles.size()+" test competency profiles from test competency manifest. Optional agent functionality enabled."); - } catch (FileNotFoundException e) { - LOG.info("Test competency manifest file for agent feedback not found. Skipping optional functionality."); - testCompetencyProfiles=null; - } catch (IOException e) { - LOG.info("Test competency manifest file for agent feedback unreadable. Skipping optional functionality."); - testCompetencyProfiles=null; - } - return testCompetencyProfiles; - } - -} +package de.hftstuttgart.dtabackend.utils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.net.MalformedURLException; +import java.nio.file.Path; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.exc.StreamReadException; +import com.fasterxml.jackson.databind.DatabindException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import de.hftstuttgart.dtabackend.models.Result; +import de.hftstuttgart.dtabackend.models.ResultSummary; +import de.hftstuttgart.dtabackend.models.TestCompetencyProfile; + +import java.io.FileNotFoundException; + +public class CompetencyAssessmentUtil { + private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class); + + public static String TEST_COMPETENCY_MANIFEST_FILE_NAME="competency-tests.mft"; + + public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException { + ResultSummary summary=ExecuteTestUtil.generateResult(Path.of(args[0]), Path.of(args[1])); + System.out.println(summary.successfulTestCompetencyProfile); + } + + public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) { + float[] tcpTotalProfile=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS]; + for(TestCompetencyProfile currentProfile: testCompetencyProfiles) { + tcpTotalProfile=TestCompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments); + } + return tcpTotalProfile; + } + + public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary) { + float[] sumSuccessful=new float[TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS]; + for(Result currentResult: resultSummary.results) { + if(currentResult.state==Result.State.SUCCESS.ordinal()) { + TestCompetencyProfile currentProfile=new TestCompetencyProfile(); + currentProfile.testPackageName=(currentResult.packageName!=null)?currentResult.packageName:""; + currentProfile.testClassName=(currentResult.className!=null)?currentResult.className:""; + currentProfile.testName=(currentResult.name!=null)?currentResult.name:""; + int testIndex=testCompetencyProfiles.indexOf(currentProfile); + if(testIndex!=-1) { + sumSuccessful=TestCompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments); + } + } + } + return sumSuccessful; + } + + public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path testPath, String fileName) { + List<TestCompetencyProfile> testCompetencyProfiles=new ArrayList<TestCompetencyProfile>(); + try { + BufferedReader testCompetencyManifest=new BufferedReader(new FileReader(new File(testPath.toFile(), fileName))); + String testEntry=testCompetencyManifest.readLine(); + while(testEntry!=null) + { + String[] testEntyComponents=testEntry.split(TestCompetencyProfile.COMPETENCY_SEPARATOR); + TestCompetencyProfile currentProfile=new TestCompetencyProfile(); + currentProfile.testPackageName=testEntyComponents[0]; + currentProfile.testClassName=testEntyComponents[1]; + currentProfile.testName=testEntyComponents[2]; + for(int competencyIndex=0; competencyIndex<TestCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) { + currentProfile.competencyAssessments[competencyIndex]=Float.valueOf(testEntyComponents[competencyIndex+3]); + } + testCompetencyProfiles.add(currentProfile); + testEntry=testCompetencyManifest.readLine(); + } + testCompetencyManifest.close(); + LOG.info("Added "+testCompetencyProfiles.size()+" test competency profiles from test competency manifest. Optional agent functionality enabled."); + } catch (FileNotFoundException e) { + LOG.info("Test competency manifest file for agent feedback not found. Skipping optional functionality."); + testCompetencyProfiles=null; + } catch (IOException e) { + LOG.info("Test competency manifest file for agent feedback unreadable. Skipping optional functionality."); + testCompetencyProfiles=null; + } + return testCompetencyProfiles; + } + + public static String packFloats(float[] array) { + return IntStream.range(0, array.length) + .mapToObj(i -> String.valueOf(array[i])) + .collect(Collectors.joining(";")); + } + +} diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java index 1e496cfac644dff14df71865f3d04dbb86841442..8318f734d3ff2662588c1ecf059396a48fcd3494 100644 --- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java +++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java @@ -1,143 +1,145 @@ -package de.hftstuttgart.dtabackend.utils; - -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.ResultSummary; -import de.hftstuttgart.dtabackend.models.TestCompetencyProfile; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.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.List; -import java.util.regex.Matcher; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -@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 testTmpPath; - - 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("data.dir"), - env.getProperty("data.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.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir")); - } - - public ResultSummary 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.findProfessorConfig( - 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("/data/test")), - new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/data/src")), - new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/data/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("Could not find result file in %s", resultFile.getAbsolutePath())); - throw new RuntimeException("no resultfile found"); - } - - LOG.debug("parse results json"); - ObjectMapper objectMapper = new ObjectMapper(); - ResultSummary resultSummary = objectMapper.readValue( - resultFile.toURI().toURL(), - ResultSummary.class); - - LOG.info("Checking for optional test competency profile information for paedagogical agent functionality..."); - List<TestCompetencyProfile> testCompetencyProfiles=CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME); - if(testCompetencyProfiles!=null) { - LOG.info("Found optional test competency profiles, generating agent profile data..."); - resultSummary.overallTestCompetencyProfile=packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles)); - resultSummary.successfulTestCompetencyProfile=packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary)); - } - - return resultSummary; - } - - private static String packFloats(float[] array) { - return IntStream.range(0, array.length) - .mapToObj(i -> String.valueOf(array[i])) - .collect(Collectors.joining(";")); - } -} +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.ResultSummary; +import de.hftstuttgart.dtabackend.models.TestCompetencyProfile; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.io.*; +import java.net.MalformedURLException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +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 testTmpPath; + + 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("data.dir"), + env.getProperty("data.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.testTmpPath = Paths.get(env.getProperty("tests.tmp.dir")); + } + + public ResultSummary 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.findProfessorConfig( + 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("/data/test")), + new Bind(srcPathHost.toAbsolutePath().toString(), new Volume("/data/src")), + new Bind(resultPathHost.toAbsolutePath().toString(), new Volume("/data/result")) + ); + + ResultSummary resultSummary = generateResult(resultPath, testPathHost); + + return resultSummary; + } + + static ResultSummary generateResult(Path resultPath, Path testPathHost) + throws IOException, StreamReadException, DatabindException, MalformedURLException { + // 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("Could not find result file in %s", resultFile.getAbsolutePath())); + throw new RuntimeException("no resultfile found"); + } + + LOG.debug("parse 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.info("Checking for optional test competency profile information for paedagogical agent functionality..."); + List<TestCompetencyProfile> testCompetencyProfiles=CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.TEST_COMPETENCY_MANIFEST_FILE_NAME); + if(testCompetencyProfiles!=null) { + LOG.info("Found optional test competency profiles, generating agent profile data..."); + resultSummary.overallTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles)); + resultSummary.successfulTestCompetencyProfile=CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary)); + } + return resultSummary; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 06b5301807cb0b0fef8a2f8edeab3eaf31744e38..af9a4d1640556207233c6f1b2203e634fd1a25ff 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -7,7 +7,7 @@ spring.http.multipart.max-file-size=5Mb ############################################### # Holds the uploaded Zip-Files -tests.tmp.dir=/tmp/dtt-tests +tests.tmp.dir=~/dta-tests host.tests.tmp.dir=${tests.tmp.dir} data.dir=/data data.dir.test.folder.name=UnitTests