From 375b37bf188ffd26fdd0bf0d7aeab0170b63ea32 Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Wed, 13 Nov 2024 17:56:41 +0100
Subject: [PATCH 1/8] Refactor for backtracking manifesto

---
 .../utils/CompetencyAssessmentUtil.java       | 284 ++++++++------
 .../dtabackend/utils/ExecuteTestUtil.java     | 345 ++++++++----------
 2 files changed, 321 insertions(+), 308 deletions(-)

diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
index cdc3b43..4fcd484 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
@@ -3,134 +3,188 @@ 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.Files;
 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 de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
 import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 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 String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME="exercise-tests.mft";
-	
-	/*public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException {
-		ResultSummary summary=ExecuteTestUtil.generateResult("1", Path.of(args[0]), Path.of(args[1]));
-        System.out.println(summary.successfulTestCompetencyProfile);
-	}
-	*/
-	public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
-		float[] tcpTotalProfile=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-		for(TestCompetencyProfile currentProfile: testCompetencyProfiles) {
-			tcpTotalProfile=ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
-		}
-		return tcpTotalProfile;
-	}
-	
-	public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
-		float[] sumSuccessful=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-		for(Result currentResult: resultSummary.results) {
-			boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
-	        if (isSuccess == success) {
-				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=ICompetencyProfile.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(ICompetencyProfile.COMPETENCY_SEPARATOR);
-				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
-				currentProfile.testPackageName=testEntyComponents[0];
-				currentProfile.testClassName=testEntyComponents[1];
-				currentProfile.testName=testEntyComponents[2];
-				for(int competencyIndex=0; competencyIndex<ICompetencyProfile.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 List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
-	    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
-
-	    try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(new File(exercisePath.toFile(), fileName)))) {
-	        String exerciseEntry = exerciseCompetencyManifest.readLine();
-
-	        while (exerciseEntry != null) {
-	            String[] exerciseEntyComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
-	            ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
-
-	            currentProfile.exerciseTopicName = exerciseEntyComponents[0];
-	            currentProfile.exerciseName = exerciseEntyComponents[1];
-	            currentProfile.exerciseURL = exerciseEntyComponents[2];
-	            
-	            for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-	                currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntyComponents[competencyIndex+3]);
-	            }
-	            
-	            currentProfile.difficulty = Float.parseFloat(exerciseEntyComponents[19]);
-
-	            exerciseCompetencyProfiles.add(currentProfile);
-	            
-	            exerciseEntry = exerciseCompetencyManifest.readLine();
-	        }
-	        exerciseCompetencyManifest.close();
-	        LOG.info("Added " + exerciseCompetencyProfiles.size() + " test competency profiles from exercise competency manifest.");
-	    } catch (FileNotFoundException e) {
-	        LOG.info("Exercise competency manifest file not found.");
-	    } catch (IOException e) {
-	        LOG.info("Exercise competency manifest file unreadable.");
-	    }
-
-	    return exerciseCompetencyProfiles;
-	}
-
-	public static String packFloats(float[] array) {
-		return IntStream.range(0, array.length)
-	            .mapToObj(i -> String.valueOf(array[i]))
-	            .collect(Collectors.joining(";"));
-	}
+    private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
+
+    public static final String TEST_COMPETENCY_MANIFEST_FILE_NAME = "competency-tests.mft";
+    public static final String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME = "exercise-tests.mft";
+
+    /**
+     * Retrieves the base directory where the test competency manifest is located
+     * by traversing upwards until it finds the file "exercise-tests.mft".
+     * 
+     * @param startDir Starting directory path to begin the search
+     * @return Path of the base directory if found; otherwise, null
+     */
+    public static Path getBaseDirectory(Path startDir) {
+        Path currentDir = startDir;
+        while (currentDir != null) {
+            Path manifestPath = currentDir.resolve(EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+            if (Files.exists(manifestPath)) {
+                return currentDir;
+            }
+            currentDir = currentDir.getParent(); 
+        }
+        LOG.warn("Base directory with " + EXERCISE_COMPETENCY_MANIFEST_FILE_NAME + " not found starting from " + startDir);
+        return null;
+    }
+
+    /**
+     * Reads and loads test competency profiles from the base directory.
+     *
+     * @param exercisePath Path to the starting exercise directory
+     * @return List of TestCompetencyProfile instances loaded from the manifest
+     */
+    public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path exercisePath) {
+        Path baseDir = getBaseDirectory(exercisePath);
+        if (baseDir == null) {
+            LOG.error("Unable to locate the base directory for reading test competency profiles.");
+            return null;
+        }
+        return readTestCompetencyProfiles(baseDir, TEST_COMPETENCY_MANIFEST_FILE_NAME);
+    }
+
+    /**
+     * Reads and loads exercise competency profiles from the specified exercise directory.
+     *
+     * @param exercisePath Path to the exercise directory
+     * @return List of ExerciseCompetencyProfile instances loaded from the manifest
+     */
+    public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath) {
+        return readExerciseCompetencyProfiles(exercisePath, EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+    }
+
+    private static List<TestCompetencyProfile> readTestCompetencyProfiles(Path baseDir, String fileName) {
+        List<TestCompetencyProfile> testCompetencyProfiles = new ArrayList<>();
+        Path manifestPath = baseDir.resolve(fileName);
+
+        try (BufferedReader testCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
+            String testEntry = testCompetencyManifest.readLine();
+            while (testEntry != null) {
+                String[] testEntryComponents = testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR);
+                TestCompetencyProfile currentProfile = new TestCompetencyProfile();
+                currentProfile.testPackageName = testEntryComponents[0];
+                currentProfile.testClassName = testEntryComponents[1];
+                currentProfile.testName = testEntryComponents[2];
+                for (int competencyIndex = 0; competencyIndex < ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(testEntryComponents[competencyIndex + 3]);
+                }
+                testCompetencyProfiles.add(currentProfile);
+                testEntry = testCompetencyManifest.readLine();
+            }
+            LOG.info("Loaded " + testCompetencyProfiles.size() + " test competency profiles from " + manifestPath);
+        } catch (IOException e) {
+            LOG.error("Error reading test competency manifest file at " + manifestPath, e);
+        }
+
+        return testCompetencyProfiles;
+    }
+
+    private static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
+        List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
+        Path manifestPath = exercisePath.resolve(fileName);
+
+        try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
+            String exerciseEntry = exerciseCompetencyManifest.readLine();
+            while (exerciseEntry != null) {
+                String[] exerciseEntryComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
+                ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
+                currentProfile.exerciseTopicName = exerciseEntryComponents[0];
+                currentProfile.exerciseName = exerciseEntryComponents[1];
+                currentProfile.exerciseURL = exerciseEntryComponents[2];
+                for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntryComponents[competencyIndex + 3]);
+                }
+                currentProfile.difficulty = Float.parseFloat(exerciseEntryComponents[19]);
+                exerciseCompetencyProfiles.add(currentProfile);
+                exerciseEntry = exerciseCompetencyManifest.readLine();
+            }
+            LOG.info("Loaded " + exerciseCompetencyProfiles.size() + " exercise competency profiles from " + manifestPath);
+        } catch (IOException e) {
+            LOG.error("Error reading exercise competency manifest file at " + manifestPath, e);
+        }
+
+        return exerciseCompetencyProfiles;
+    }
+
+    /**
+     * Converts an array of floats into a semicolon-separated string.
+     *
+     * @param array Array of float values to pack
+     * @return Semicolon-separated string of float values
+     */
+    public static String packFloats(float[] array) {
+        return IntStream.range(0, array.length)
+                .mapToObj(i -> String.valueOf(array[i]))
+                .collect(Collectors.joining(";"));
+    }
+
+    /**
+     * Sums the competency profiles across all test competency profiles.
+     *
+     * @param testCompetencyProfiles List of test competency profiles
+     * @return An array representing the sum of competencies
+     */
+    public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
+        float[] tcpTotalProfile = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+        for (TestCompetencyProfile currentProfile : testCompetencyProfiles) {
+            tcpTotalProfile = ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
+        }
+        return tcpTotalProfile;
+    }
+
+    /**
+     * Sums only the successful test competency profiles, based on the results summary.
+     *
+     * @param testCompetencyProfiles List of test competency profiles
+     * @param resultSummary The result summary containing test results
+     * @param success Indicates whether to sum successful (true) or unsuccessful (false) profiles
+     * @return An array representing the sum of competencies for successful or unsuccessful profiles
+     */
+    public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
+        float[] sumSuccessful = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+        for (Result currentResult : resultSummary.results) {
+            boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
+            if (isSuccess == success) {
+                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 = ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
+                }
+            }
+        }
+        return sumSuccessful;
+    }
 
+    /**
+     * Checks if all elements in the competency profile array are zero (indicating full success).
+     *
+     * @param competencyProfile Array of competency values
+     * @return True if all values are zero; false otherwise
+     */
+    public static boolean isFullSuccess(float[] competencyProfile) {
+        for (float value : competencyProfile) {
+            if (value != 0.0f) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 333b177..73e5ced 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -7,7 +7,6 @@ import com.github.dockerjava.api.model.Bind;
 import com.github.dockerjava.api.model.Volume;
 
 import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
-import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 import de.hftstuttgart.dtabackend.models.Recommendation;
 import de.hftstuttgart.dtabackend.models.ResultSummary;
 import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
@@ -22,10 +21,7 @@ import java.net.MalformedURLException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -38,217 +34,180 @@ public class ExecuteTestUtil  {
     private final String assignmentBasePath;
     private final Path testTmpPathHost;
 
-    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(
+        this.assignmentBasePath = Paths.get(
             env.getProperty("data.dir"),
-            env.getProperty("data.dir.test.folder.name"));
-        this.assignmentBasePath = p.toAbsolutePath().toString();
+            env.getProperty("data.dir.test.folder.name"))
+            .toAbsolutePath().toString();
 
-        // set path of temporary directory on host _and_ inside our container, _must_ be identical 
         this.testTmpPathHost = Paths.get(env.getProperty("host.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"));
-
+        // Define paths
+        Path testPath = workDirectory.resolve("test");
+        Path srcPath = workDirectory.resolve("src");
+        Path resultPath = workDirectory.resolve("result");
+
+        // Clone test to temporary directory
+        LOG.debug("Copying pre-downloaded unittest repo");
+        FileUtil.copyFolder(Paths.get(assignmentBasePath, assignmentId), testPath);
+
+        // Copy configuration file
+        LOG.debug("Copy test config");
+        Files.copy(Paths.get(assignmentBasePath, assignmentId + ".txt"), workDirectory.resolve("config.txt"));
         Files.createDirectory(resultPath);
 
-        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="";
-        if(config==null)
-        {
-        	config = RegexUtil.extractConfig(
-                    new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.TESTCONFIGREGEX));
-        	if(config==null)
-        	{
-           		throw new RuntimeException("couldn't find repo config for unittest image extraction");
-        	}
-        	image=config.group(4);
-        }
-        else
-        {
-        	image=config.group(5);
-        }
-        // 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()
-        );
+        // Load container image configuration
+        LOG.info("Reading test config");
+        String image = loadImageConfig(workDirectory);
+
+        // Define paths to mount in the container
+        Path testPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(testPath.getFileName());
+        Path srcPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(srcPath.getFileName());
+        Path resultPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(resultPath.getFileName());
 
-        // start test-container with professor given image and bind mounts for test, submission and result
+        // Start container with mounts
         dockerUtil.runContainer(
             image,
-            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"))
+            new Bind(testPathHost.toString(), new Volume("/data/test")),
+            new Bind(srcPathHost.toString(), new Volume("/data/src")),
+            new Bind(resultPathHost.toString(), new Volume("/data/result"))
         );
 
-        ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPathHost);
-        
-        return resultSummary;
+        return generateResult(assignmentId, resultPath, testPathHost);
+    }
+
+    private String loadImageConfig(Path workDirectory) throws FileNotFoundException {
+        Matcher config = RegexUtil.extractConfig(
+            new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
+            Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX));
+
+        if (config == null) {
+            config = RegexUtil.extractConfig(
+                new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
+                Pattern.compile(RegexUtil.TESTCONFIGREGEX));
+            
+            if (config == null) {
+                throw new RuntimeException("Couldn't find repo config for unittest image extraction");
+            }
+            return config.group(4);
+        }
+        return config.group(5);
     }
 
-	private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
-			throws IOException, StreamReadException, DatabindException, MalformedURLException {
-		// define expected result file
-        File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
+    private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
+            throws IOException, StreamReadException, DatabindException, MalformedURLException {
+        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()));
-            throw new RuntimeException("no resultfile found");
+            throw new RuntimeException("No result file found");
         }
 
-        LOG.debug("parse results json");
+        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.debug("Result JSON returned at " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results.");
+
+        LOG.info("Checking for optional test competency profile information...");
+        List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost);
+
+        if (testCompetencyProfiles != null) {
+            LOG.info("Found test competency profiles, generating profile data...");
+            resultSummary.overallTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles));
+            resultSummary.successfulTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true));
+            
+            Path exerciseManifestFile = testPathHost.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+            if (Files.exists(exerciseManifestFile)) {
+                LOG.info("Found exercise competency profiles, generating recommendations...");
+                resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
+            }
+        }
+        return resultSummary;
+    }
+
+    public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
+        throws FileNotFoundException {
+    
+    String testRepoURL = RegexUtil.extractConfig(
+        new FileInputStream(Paths.get(assignmentBasePath, assignmentId + ".txt").toFile()), 
+        Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)).group(1);
+
+    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost);
+
+    int currentTopicIndex = 0;
+    float currentDifficulty = 0.0f;
+    Map<String, Integer> topicOrder = new HashMap<>();
+    int order = 1;
+
+    // Determine currentTopicIndex and set currentDifficulty based on exercise profiles
+    for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
+        if (!topicOrder.containsKey(e.exerciseTopicName)) {
+            topicOrder.put(e.exerciseTopicName, order++);
+        }
+        if (e.exerciseURL.equals(testRepoURL)) {
+            currentTopicIndex = order;
+            currentDifficulty = e.difficulty;  // Directly assign to currentDifficulty
+        }
+    }
+
+    // Sum competencies for unsuccessful tests
+    float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
+
+    // Filter exercises based on topics and difficulty
+    List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
+            exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, currentDifficulty, unsuccessful);
+
+    // Generate recommendations without using lambda expressions
+    List<Recommendation> recommendedExercises = new ArrayList<>();
+    for (ExerciseCompetencyProfile profile : filteredExercises) {
+        float score = calculateScore(profile, unsuccessful, topicOrder, currentDifficulty);
+        Recommendation recommendation = new Recommendation(
+                profile.exerciseTopicName, 
+                profile.exerciseURL, 
+                profile.exerciseName, 
+                profile.difficulty, 
+                score);
+        recommendedExercises.add(recommendation);
+    }
+
+    // Sort the recommendations using Collections.sort and a comparator
+    Collections.sort(recommendedExercises, Recommendation.COMPARE_BY_SCORE);
+    
+    return recommendedExercises;
+}
+
+    public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> profiles,
+            Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful) {
         
-        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, true));
-        	
-        	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
-        	//testPathHost or assignmentBasePath
-        	Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-			if (Files.exists(exerciseManifestFile)) {
-				LOG.info("Found optional exercise competency profiles, generating recommendations...");
-				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
-			}
+        List<ExerciseCompetencyProfile> filteredExercises = profiles.stream()
+                .filter(profile -> topicOrder.get(profile.exerciseTopicName) <= currentTopicIndex)
+                .collect(Collectors.toList());
+
+        if (CompetencyAssessmentUtil.isFullSuccess(unsuccessful)) {
+            filteredExercises = filteredExercises.stream()
+                    .filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL))
+                    .collect(Collectors.toList());
+        } else {
+            filteredExercises = filteredExercises.stream()
+                    .filter(profile -> profile.difficulty <= currentDifficulty)
+                    .collect(Collectors.toList());
         }
-		return resultSummary;
-	}
-	
-	/*
-	 * exercise recommendation part
-	 */
-	public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, 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);
-
-		List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-
-		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;
-			}
-		}
-		
-		//filter exercises according to success
-		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) {
-			Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName,
-					exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty));
-			recommendedExercises.add(recommendation);
-			LOG.info("Recommending exercise "+recommendation.topic+"/"+recommendation.exerciseName+" with score "+recommendation.score);
-		}
-		//sort the recommendations for successful or resilient learners, otherwise reverse in display
-		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, 
-			ResultSummary resultSummary) {
-		//filter out all advanced topics in any case
-		//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());
-
-		//filter by difficulty according to success
-		if (isFullSuccess(unsuccessful)) {
-			filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList());
-		} 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 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;
-		//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)));
-		
-		//topic component
-		score *= topicOrder.get(exerciseProfile.exerciseTopicName);
-
-		score = Math.round(score * 10.0f) / 10.0f;
-		return score;
-	}
+
+        return filteredExercises;
+    }
+
+    public static float calculateScore(ExerciseCompetencyProfile profile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) {
+        float score = 1.0f;
+        for (int i = 0; i < profile.competencyAssessments.length - 1; i++) {
+            score += profile.competencyAssessments[i] * unsuccessful[i];
+        }
+
+        score *= profile.difficulty * (0.5f + Math.abs(currentDifficulty - profile.difficulty));
+        score *= topicOrder.getOrDefault(profile.exerciseTopicName, 1);
+        
+        return Math.round(score * 10.0f) / 10.0f;
+    }
 }
\ No newline at end of file
-- 
GitLab


From 33313e942cacd5965e709aa532e4fddcf6b102a3 Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Tue, 19 Nov 2024 14:38:40 +0100
Subject: [PATCH 2/8] Revert "Refactor for backtracking manifesto"

This reverts commit 375b37bf188ffd26fdd0bf0d7aeab0170b63ea32.
---
 .../utils/CompetencyAssessmentUtil.java       | 284 ++++++--------
 .../dtabackend/utils/ExecuteTestUtil.java     | 345 ++++++++++--------
 2 files changed, 308 insertions(+), 321 deletions(-)

diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
index 4fcd484..cdc3b43 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
@@ -3,188 +3,134 @@ package de.hftstuttgart.dtabackend.utils;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
-import java.nio.file.Files;
+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 de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
 import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 import de.hftstuttgart.dtabackend.models.Result;
 import de.hftstuttgart.dtabackend.models.ResultSummary;
 import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
 
-public class CompetencyAssessmentUtil {
-    private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
-
-    public static final String TEST_COMPETENCY_MANIFEST_FILE_NAME = "competency-tests.mft";
-    public static final String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME = "exercise-tests.mft";
-
-    /**
-     * Retrieves the base directory where the test competency manifest is located
-     * by traversing upwards until it finds the file "exercise-tests.mft".
-     * 
-     * @param startDir Starting directory path to begin the search
-     * @return Path of the base directory if found; otherwise, null
-     */
-    public static Path getBaseDirectory(Path startDir) {
-        Path currentDir = startDir;
-        while (currentDir != null) {
-            Path manifestPath = currentDir.resolve(EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-            if (Files.exists(manifestPath)) {
-                return currentDir;
-            }
-            currentDir = currentDir.getParent(); 
-        }
-        LOG.warn("Base directory with " + EXERCISE_COMPETENCY_MANIFEST_FILE_NAME + " not found starting from " + startDir);
-        return null;
-    }
-
-    /**
-     * Reads and loads test competency profiles from the base directory.
-     *
-     * @param exercisePath Path to the starting exercise directory
-     * @return List of TestCompetencyProfile instances loaded from the manifest
-     */
-    public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path exercisePath) {
-        Path baseDir = getBaseDirectory(exercisePath);
-        if (baseDir == null) {
-            LOG.error("Unable to locate the base directory for reading test competency profiles.");
-            return null;
-        }
-        return readTestCompetencyProfiles(baseDir, TEST_COMPETENCY_MANIFEST_FILE_NAME);
-    }
-
-    /**
-     * Reads and loads exercise competency profiles from the specified exercise directory.
-     *
-     * @param exercisePath Path to the exercise directory
-     * @return List of ExerciseCompetencyProfile instances loaded from the manifest
-     */
-    public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath) {
-        return readExerciseCompetencyProfiles(exercisePath, EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-    }
-
-    private static List<TestCompetencyProfile> readTestCompetencyProfiles(Path baseDir, String fileName) {
-        List<TestCompetencyProfile> testCompetencyProfiles = new ArrayList<>();
-        Path manifestPath = baseDir.resolve(fileName);
+import java.io.FileNotFoundException;
 
-        try (BufferedReader testCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
-            String testEntry = testCompetencyManifest.readLine();
-            while (testEntry != null) {
-                String[] testEntryComponents = testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR);
-                TestCompetencyProfile currentProfile = new TestCompetencyProfile();
-                currentProfile.testPackageName = testEntryComponents[0];
-                currentProfile.testClassName = testEntryComponents[1];
-                currentProfile.testName = testEntryComponents[2];
-                for (int competencyIndex = 0; competencyIndex < ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(testEntryComponents[competencyIndex + 3]);
-                }
-                testCompetencyProfiles.add(currentProfile);
-                testEntry = testCompetencyManifest.readLine();
-            }
-            LOG.info("Loaded " + testCompetencyProfiles.size() + " test competency profiles from " + manifestPath);
-        } catch (IOException e) {
-            LOG.error("Error reading test competency manifest file at " + manifestPath, e);
-        }
-
-        return testCompetencyProfiles;
-    }
-
-    private static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
-        List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
-        Path manifestPath = exercisePath.resolve(fileName);
-
-        try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
-            String exerciseEntry = exerciseCompetencyManifest.readLine();
-            while (exerciseEntry != null) {
-                String[] exerciseEntryComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
-                ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
-                currentProfile.exerciseTopicName = exerciseEntryComponents[0];
-                currentProfile.exerciseName = exerciseEntryComponents[1];
-                currentProfile.exerciseURL = exerciseEntryComponents[2];
-                for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntryComponents[competencyIndex + 3]);
-                }
-                currentProfile.difficulty = Float.parseFloat(exerciseEntryComponents[19]);
-                exerciseCompetencyProfiles.add(currentProfile);
-                exerciseEntry = exerciseCompetencyManifest.readLine();
-            }
-            LOG.info("Loaded " + exerciseCompetencyProfiles.size() + " exercise competency profiles from " + manifestPath);
-        } catch (IOException e) {
-            LOG.error("Error reading exercise competency manifest file at " + manifestPath, e);
-        }
-
-        return exerciseCompetencyProfiles;
-    }
-
-    /**
-     * Converts an array of floats into a semicolon-separated string.
-     *
-     * @param array Array of float values to pack
-     * @return Semicolon-separated string of float values
-     */
-    public static String packFloats(float[] array) {
-        return IntStream.range(0, array.length)
-                .mapToObj(i -> String.valueOf(array[i]))
-                .collect(Collectors.joining(";"));
-    }
-
-    /**
-     * Sums the competency profiles across all test competency profiles.
-     *
-     * @param testCompetencyProfiles List of test competency profiles
-     * @return An array representing the sum of competencies
-     */
-    public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
-        float[] tcpTotalProfile = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-        for (TestCompetencyProfile currentProfile : testCompetencyProfiles) {
-            tcpTotalProfile = ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
-        }
-        return tcpTotalProfile;
-    }
-
-    /**
-     * Sums only the successful test competency profiles, based on the results summary.
-     *
-     * @param testCompetencyProfiles List of test competency profiles
-     * @param resultSummary The result summary containing test results
-     * @param success Indicates whether to sum successful (true) or unsuccessful (false) profiles
-     * @return An array representing the sum of competencies for successful or unsuccessful profiles
-     */
-    public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
-        float[] sumSuccessful = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-        for (Result currentResult : resultSummary.results) {
-            boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
-            if (isSuccess == success) {
-                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 = ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
-                }
-            }
-        }
-        return sumSuccessful;
-    }
+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 String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME="exercise-tests.mft";
+	
+	/*public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException {
+		ResultSummary summary=ExecuteTestUtil.generateResult("1", Path.of(args[0]), Path.of(args[1]));
+        System.out.println(summary.successfulTestCompetencyProfile);
+	}
+	*/
+	public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
+		float[] tcpTotalProfile=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+		for(TestCompetencyProfile currentProfile: testCompetencyProfiles) {
+			tcpTotalProfile=ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
+		}
+		return tcpTotalProfile;
+	}
+	
+	public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
+		float[] sumSuccessful=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+		for(Result currentResult: resultSummary.results) {
+			boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
+	        if (isSuccess == success) {
+				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=ICompetencyProfile.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(ICompetencyProfile.COMPETENCY_SEPARATOR);
+				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
+				currentProfile.testPackageName=testEntyComponents[0];
+				currentProfile.testClassName=testEntyComponents[1];
+				currentProfile.testName=testEntyComponents[2];
+				for(int competencyIndex=0; competencyIndex<ICompetencyProfile.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 List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
+	    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
+
+	    try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(new File(exercisePath.toFile(), fileName)))) {
+	        String exerciseEntry = exerciseCompetencyManifest.readLine();
+
+	        while (exerciseEntry != null) {
+	            String[] exerciseEntyComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
+	            ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
+
+	            currentProfile.exerciseTopicName = exerciseEntyComponents[0];
+	            currentProfile.exerciseName = exerciseEntyComponents[1];
+	            currentProfile.exerciseURL = exerciseEntyComponents[2];
+	            
+	            for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+	                currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntyComponents[competencyIndex+3]);
+	            }
+	            
+	            currentProfile.difficulty = Float.parseFloat(exerciseEntyComponents[19]);
+
+	            exerciseCompetencyProfiles.add(currentProfile);
+	            
+	            exerciseEntry = exerciseCompetencyManifest.readLine();
+	        }
+	        exerciseCompetencyManifest.close();
+	        LOG.info("Added " + exerciseCompetencyProfiles.size() + " test competency profiles from exercise competency manifest.");
+	    } catch (FileNotFoundException e) {
+	        LOG.info("Exercise competency manifest file not found.");
+	    } catch (IOException e) {
+	        LOG.info("Exercise competency manifest file unreadable.");
+	    }
+
+	    return exerciseCompetencyProfiles;
+	}
+
+	public static String packFloats(float[] array) {
+		return IntStream.range(0, array.length)
+	            .mapToObj(i -> String.valueOf(array[i]))
+	            .collect(Collectors.joining(";"));
+	}
 
-    /**
-     * Checks if all elements in the competency profile array are zero (indicating full success).
-     *
-     * @param competencyProfile Array of competency values
-     * @return True if all values are zero; false otherwise
-     */
-    public static boolean isFullSuccess(float[] competencyProfile) {
-        for (float value : competencyProfile) {
-            if (value != 0.0f) {
-                return false;
-            }
-        }
-        return true;
-    }
 }
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 73e5ced..333b177 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -7,6 +7,7 @@ import com.github.dockerjava.api.model.Bind;
 import com.github.dockerjava.api.model.Volume;
 
 import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
+import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 import de.hftstuttgart.dtabackend.models.Recommendation;
 import de.hftstuttgart.dtabackend.models.ResultSummary;
 import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
@@ -21,7 +22,10 @@ import java.net.MalformedURLException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -34,180 +38,217 @@ public class ExecuteTestUtil  {
     private final String assignmentBasePath;
     private final Path testTmpPathHost;
 
-    public ExecuteTestUtil(Environment env, DockerUtil dockerUtil) {
+    public ExecuteTestUtil(
+        Environment env,
+        DockerUtil dockerUtil
+    ) {
         this.dockerUtil = dockerUtil;
-        this.assignmentBasePath = Paths.get(
+
+        // set base path for assignments to be stored
+        Path p = Paths.get(
             env.getProperty("data.dir"),
-            env.getProperty("data.dir.test.folder.name"))
-            .toAbsolutePath().toString();
+            env.getProperty("data.dir.test.folder.name"));
+        this.assignmentBasePath = p.toAbsolutePath().toString();
 
+        // set path of temporary directory on host _and_ inside our container, _must_ be identical 
         this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
     }
 
     public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
-        // Define paths
-        Path testPath = workDirectory.resolve("test");
-        Path srcPath = workDirectory.resolve("src");
-        Path resultPath = workDirectory.resolve("result");
-
-        // Clone test to temporary directory
-        LOG.debug("Copying pre-downloaded unittest repo");
-        FileUtil.copyFolder(Paths.get(assignmentBasePath, assignmentId), testPath);
-
-        // Copy configuration file
-        LOG.debug("Copy test config");
-        Files.copy(Paths.get(assignmentBasePath, assignmentId + ".txt"), workDirectory.resolve("config.txt"));
-        Files.createDirectory(resultPath);
 
-        // Load container image configuration
-        LOG.info("Reading test config");
-        String image = loadImageConfig(workDirectory);
+        // 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");
 
-        // Define paths to mount in the container
-        Path testPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(testPath.getFileName());
-        Path srcPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(srcPath.getFileName());
-        Path resultPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(resultPath.getFileName());
+        // clone stored test to tmpdir
+        LOG.debug("copying pre-downloaded unitttest repo");
+        FileUtil.copyFolder(
+            Paths.get(assignmentBasePath, assignmentId),
+            testPath);
 
-        // Start container with mounts
-        dockerUtil.runContainer(
-            image,
-            new Bind(testPathHost.toString(), new Volume("/data/test")),
-            new Bind(srcPathHost.toString(), new Volume("/data/src")),
-            new Bind(resultPathHost.toString(), new Volume("/data/result"))
-        );
+        LOG.debug("copy test config");
+        Files.copy(
+            Paths.get(assignmentBasePath, assignmentId + ".txt"),
+            Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt"));
 
-        return generateResult(assignmentId, resultPath, testPathHost);
-    }
+        Files.createDirectory(resultPath);
 
-    private String loadImageConfig(Path workDirectory) throws FileNotFoundException {
+        LOG.info("reading test config");
         Matcher config = RegexUtil.extractConfig(
-            new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
-            Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX));
-
-        if (config == null) {
-            config = RegexUtil.extractConfig(
-                new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
-                Pattern.compile(RegexUtil.TESTCONFIGREGEX));
-            
-            if (config == null) {
-                throw new RuntimeException("Couldn't find repo config for unittest image extraction");
-            }
-            return config.group(4);
+            new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "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));
+        	if(config==null)
+        	{
+           		throw new RuntimeException("couldn't find repo config for unittest image extraction");
+        	}
+        	image=config.group(4);
+        }
+        else
+        {
+        	image=config.group(5);
         }
-        return config.group(5);
+        // 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(
+            image,
+            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(assignmentId, resultPath, testPathHost);
+        
+        return resultSummary;
     }
 
-    private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
-            throws IOException, StreamReadException, DatabindException, MalformedURLException {
-        File resultFile = resultPath.resolve("result.json").toFile();
+	private ResultSummary generateResult(String assignmentId, 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 result file found");
+            throw new RuntimeException("no resultfile found");
         }
 
-        LOG.debug("Parse results JSON");
+        LOG.debug("parse results json");
         ObjectMapper objectMapper = new ObjectMapper();
         ResultSummary resultSummary = objectMapper.readValue(resultFile.toURI().toURL(), ResultSummary.class);
-        LOG.debug("Result JSON returned at " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results.");
-
-        LOG.info("Checking for optional test competency profile information...");
-        List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost);
-
-        if (testCompetencyProfiles != null) {
-            LOG.info("Found test competency profiles, generating profile data...");
-            resultSummary.overallTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles));
-            resultSummary.successfulTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true));
-            
-            Path exerciseManifestFile = testPathHost.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-            if (Files.exists(exerciseManifestFile)) {
-                LOG.info("Found exercise competency profiles, generating recommendations...");
-                resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
-            }
-        }
-        return resultSummary;
-    }
-
-    public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
-        throws FileNotFoundException {
-    
-    String testRepoURL = RegexUtil.extractConfig(
-        new FileInputStream(Paths.get(assignmentBasePath, assignmentId + ".txt").toFile()), 
-        Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)).group(1);
-
-    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost);
-
-    int currentTopicIndex = 0;
-    float currentDifficulty = 0.0f;
-    Map<String, Integer> topicOrder = new HashMap<>();
-    int order = 1;
-
-    // Determine currentTopicIndex and set currentDifficulty based on exercise profiles
-    for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
-        if (!topicOrder.containsKey(e.exerciseTopicName)) {
-            topicOrder.put(e.exerciseTopicName, order++);
-        }
-        if (e.exerciseURL.equals(testRepoURL)) {
-            currentTopicIndex = order;
-            currentDifficulty = e.difficulty;  // Directly assign to currentDifficulty
-        }
-    }
-
-    // Sum competencies for unsuccessful tests
-    float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
-
-    // Filter exercises based on topics and difficulty
-    List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
-            exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, currentDifficulty, unsuccessful);
-
-    // Generate recommendations without using lambda expressions
-    List<Recommendation> recommendedExercises = new ArrayList<>();
-    for (ExerciseCompetencyProfile profile : filteredExercises) {
-        float score = calculateScore(profile, unsuccessful, topicOrder, currentDifficulty);
-        Recommendation recommendation = new Recommendation(
-                profile.exerciseTopicName, 
-                profile.exerciseURL, 
-                profile.exerciseName, 
-                profile.difficulty, 
-                score);
-        recommendedExercises.add(recommendation);
-    }
-
-    // Sort the recommendations using Collections.sort and a comparator
-    Collections.sort(recommendedExercises, Recommendation.COMPARE_BY_SCORE);
-    
-    return recommendedExercises;
-}
-
-    public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> profiles,
-            Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful) {
+        LOG.debug("result json returned time "+ resultSummary.timestamp + " with "+resultSummary.results.size()+ " test results.");
         
-        List<ExerciseCompetencyProfile> filteredExercises = profiles.stream()
-                .filter(profile -> topicOrder.get(profile.exerciseTopicName) <= currentTopicIndex)
-                .collect(Collectors.toList());
-
-        if (CompetencyAssessmentUtil.isFullSuccess(unsuccessful)) {
-            filteredExercises = filteredExercises.stream()
-                    .filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL))
-                    .collect(Collectors.toList());
-        } else {
-            filteredExercises = filteredExercises.stream()
-                    .filter(profile -> profile.difficulty <= currentDifficulty)
-                    .collect(Collectors.toList());
+        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, true));
+        	
+        	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
+        	//testPathHost or assignmentBasePath
+        	Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+			if (Files.exists(exerciseManifestFile)) {
+				LOG.info("Found optional exercise competency profiles, generating recommendations...");
+				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
+			}
         }
-
-        return filteredExercises;
-    }
-
-    public static float calculateScore(ExerciseCompetencyProfile profile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) {
-        float score = 1.0f;
-        for (int i = 0; i < profile.competencyAssessments.length - 1; i++) {
-            score += profile.competencyAssessments[i] * unsuccessful[i];
-        }
-
-        score *= profile.difficulty * (0.5f + Math.abs(currentDifficulty - profile.difficulty));
-        score *= topicOrder.getOrDefault(profile.exerciseTopicName, 1);
-        
-        return Math.round(score * 10.0f) / 10.0f;
-    }
+		return resultSummary;
+	}
+	
+	/*
+	 * exercise recommendation part
+	 */
+	public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, 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);
+
+		List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+
+		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;
+			}
+		}
+		
+		//filter exercises according to success
+		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) {
+			Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName,
+					exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty));
+			recommendedExercises.add(recommendation);
+			LOG.info("Recommending exercise "+recommendation.topic+"/"+recommendation.exerciseName+" with score "+recommendation.score);
+		}
+		//sort the recommendations for successful or resilient learners, otherwise reverse in display
+		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, 
+			ResultSummary resultSummary) {
+		//filter out all advanced topics in any case
+		//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());
+
+		//filter by difficulty according to success
+		if (isFullSuccess(unsuccessful)) {
+			filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList());
+		} 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 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;
+		//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)));
+		
+		//topic component
+		score *= topicOrder.get(exerciseProfile.exerciseTopicName);
+
+		score = Math.round(score * 10.0f) / 10.0f;
+		return score;
+	}
 }
\ No newline at end of file
-- 
GitLab


From b2f0dbbf742960c03e344c3ad68a7e347b37b267 Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Tue, 19 Nov 2024 14:41:34 +0100
Subject: [PATCH 3/8] Replaced tetsPathHost to assignmentBasePath

---
 .../java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 333b177..3f1b8dc 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -147,7 +147,7 @@ public class ExecuteTestUtil  {
         	
         	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
         	//testPathHost or assignmentBasePath
-        	Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+        	Path exerciseManifestFile = Paths.get(assignmentBasePath, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
 			if (Files.exists(exerciseManifestFile)) {
 				LOG.info("Found optional exercise competency profiles, generating recommendations...");
 				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
-- 
GitLab


From d59a136b09925df8eb944adccbeac74c138e25f1 Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Thu, 21 Nov 2024 12:01:29 +0100
Subject: [PATCH 4/8] Added debug logs and exercise-manifest copy

---
 .gitignore                                    |   2 +
 .../dtabackend/rest/v1/task/TaskUpload.java   | 219 ++++++++---------
 .../rest/v1/unittest/UnitTestUpload.java      | 229 +++++++++---------
 .../dtabackend/utils/ArchiveUtil.java         |   2 +
 .../dtabackend/utils/ExecuteTestUtil.java     |  25 +-
 .../dtabackend/utils/FileUtil.java            |   3 +
 .../dtabackend/utils/RepoUtil.java            |   2 +-
 7 files changed, 257 insertions(+), 225 deletions(-)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ac8dcdd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+/local-maven-repo
\ No newline at end of file
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 7b14090..2eec4e1 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,109 +1,110 @@
-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 java.util.regex.Pattern;
-
-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 RepoUtil repoUtil;
-    private final Path testTmpPath;
-    private final 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"));
-    }
-
-    @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, "dta");
-        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 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);
-                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 java.util.regex.Pattern;
+
+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 RepoUtil repoUtil;
+    private final Path testTmpPath;
+    private final 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"));
+    }
+
+    @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, "dta");
+        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");
+        LOG.debug(String.format("Source path defined as: %s", srcPath.toAbsolutePath().toString()));
+
+        String mimeInfo = new Tika().detect(taskFileRef.getInputStream());
+        switch (mimeInfo) {
+            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);
+                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/rest/v1/unittest/UnitTestUpload.java b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/unittest/UnitTestUpload.java
index 79cc5ed..924f982 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/rest/v1/unittest/UnitTestUpload.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/unittest/UnitTestUpload.java
@@ -1,113 +1,116 @@
-package de.hftstuttgart.dtabackend.rest.v1.unittest;
-
-import de.hftstuttgart.dtabackend.utils.RepoUtil;
-import de.hftstuttgart.dtabackend.utils.RegexUtil;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.springframework.core.env.Environment;
-import org.springframework.util.FileSystemUtils;
-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 jakarta.servlet.annotation.MultipartConfig;
-
-import java.io.*;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Rest controller for anything related to the TEST files.
- */
-@RestController
-@RequestMapping("/v1/unittest")
-@MultipartConfig
-public class UnitTestUpload {
-
-    private static final Logger LOG = LogManager.getLogger(UnitTestUpload.class);
-    private final RepoUtil repoUtil;
-    private final String assignmentBasePath;
-
-    public UnitTestUpload(Environment env, RepoUtil repoUtil) {
-        this.repoUtil = repoUtil;
-
-        Path p = Paths.get(env.getProperty("data.dir"), env.getProperty("data.dir.test.folder.name"));
-        this.assignmentBasePath = p.toAbsolutePath().toString();
-    }
-
-    /**
-     * Create a subfolder for the specific assignment.
-     * This is called when the teacher creates an assignment and uploads the JUnit test files
-     *
-     * @param unitTestFileRef The text file which contains the JUnit tests meta data
-     * @param assignmentId    ID of the created assignment. Generated by Moodle
-     */
-    @RequestMapping(method = RequestMethod.POST)
-    public void uploadUnitTestFile(
-        @RequestParam("unitTestFile") MultipartFile unitTestFileRef,
-        @RequestParam("assignmentId") String assignmentId
-    ) throws IOException {
-        LOG.info("received new assignment");
-
-        File file = Paths.get(this.assignmentBasePath, assignmentId + ".txt").toFile();
-        file.mkdirs();
-
-        // save assignment config
-        unitTestFileRef.transferTo(file);
-        LOG.debug(String.format("saved config file to: %s", file.getAbsolutePath()));
-
-        String subDir="";
-        Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
-
-        Matcher config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
-        if (config == null) {
-        	pattern=Pattern.compile(RegexUtil.TESTCONFIGREGEX);
-        	config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
-        	if(config==null)
-        	{
-        		throw new RuntimeException("couldn't find repo config for unittest clone");
-        	}
-        }
-        else {
-        	subDir=config.group(4);
-        }
-        LOG.debug("calling test repo clone");
-        // cloning assignment repo to persistent space
-        repoUtil.cloneRepository(config, Paths.get(this.assignmentBasePath, assignmentId).toAbsolutePath().toString(), subDir);
-
-        LOG.info(String.format("stored new assignment: %s", file.getAbsolutePath()));
-    }
-
-    /**
-     * Delete the folder for the assignment.
-     * Called when the teacher deletes the JUnitTest assignment
-     * <p>
-     * {{url}}:8080/v1/unittest?assignmentId=111
-     *
-     * @param assignmentId ID of the assignment to delete. Generated by Moodle
-     */
-    @RequestMapping(method = RequestMethod.DELETE)
-    public void deleteUnitTestFiles(@RequestParam("assignmentId") String assignmentId) {
-        LOG.info(String.format("received deletion order for assignment %s", assignmentId));
-
-        // deleting config file
-        File file = Paths.get(
-            this.assignmentBasePath,
-            assignmentId + ".txt")
-            .toFile();
-        file.delete();
-
-        // deleting local copy of repository
-        file = Paths.get(
-            this.assignmentBasePath,
-            assignmentId).toFile();
-        FileSystemUtils.deleteRecursively(file);
-
-        LOG.info(String.format("assignment %s deletion complete", assignmentId));
-    }
-}
+package de.hftstuttgart.dtabackend.rest.v1.unittest;
+
+import de.hftstuttgart.dtabackend.utils.RepoUtil;
+import de.hftstuttgart.dtabackend.utils.RegexUtil;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.core.env.Environment;
+import org.springframework.util.FileSystemUtils;
+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 jakarta.servlet.annotation.MultipartConfig;
+
+import java.io.*;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Rest controller for anything related to the TEST files.
+ */
+@RestController
+@RequestMapping("/v1/unittest")
+@MultipartConfig
+public class UnitTestUpload {
+
+    private static final Logger LOG = LogManager.getLogger(UnitTestUpload.class);
+    private final RepoUtil repoUtil;
+    private final String assignmentBasePath;
+
+    public UnitTestUpload(Environment env, RepoUtil repoUtil) {
+        this.repoUtil = repoUtil;
+
+        Path p = Paths.get(env.getProperty("data.dir"), env.getProperty("data.dir.test.folder.name"));
+        this.assignmentBasePath = p.toAbsolutePath().toString();
+        LOG.debug(String.format("Assignment base path initialized as: %s", this.assignmentBasePath));
+    }
+
+    /**
+     * Create a subfolder for the specific assignment.
+     * This is called when the teacher creates an assignment and uploads the JUnit test files
+     *
+     * @param unitTestFileRef The text file which contains the JUnit tests meta data
+     * @param assignmentId    ID of the created assignment. Generated by Moodle
+     */
+    @RequestMapping(method = RequestMethod.POST)
+    public void uploadUnitTestFile(
+        @RequestParam("unitTestFile") MultipartFile unitTestFileRef,
+        @RequestParam("assignmentId") String assignmentId
+    ) throws IOException {
+        LOG.info("received new assignment");
+
+        File file = Paths.get(this.assignmentBasePath, assignmentId + ".txt").toFile();
+        file.mkdirs();
+
+        // save assignment config
+        unitTestFileRef.transferTo(file);
+        LOG.debug(String.format("saved config file to: %s", file.getAbsolutePath()));
+
+        String subDir="";
+        Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
+
+        Matcher config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
+        if (config == null) {
+        	pattern=Pattern.compile(RegexUtil.TESTCONFIGREGEX);
+        	config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
+        	if(config==null)
+        	{
+        		throw new RuntimeException("couldn't find repo config for unittest clone");
+        	}
+        }
+        else {
+        	subDir=config.group(4);
+        }
+        LOG.debug("calling test repo clone");
+        // cloning assignment repo to persistent space
+        repoUtil.cloneRepository(config, Paths.get(this.assignmentBasePath, assignmentId).toAbsolutePath().toString(), subDir);
+
+        LOG.info(String.format("stored new assignment: %s", file.getAbsolutePath()));
+    }
+
+    /**
+     * Delete the folder for the assignment.
+     * Called when the teacher deletes the JUnitTest assignment
+     * <p>
+     * {{url}}:8080/v1/unittest?assignmentId=111
+     *
+     * @param assignmentId ID of the assignment to delete. Generated by Moodle
+     */
+    @RequestMapping(method = RequestMethod.DELETE)
+    public void deleteUnitTestFiles(@RequestParam("assignmentId") String assignmentId) {
+        LOG.info(String.format("received deletion order for assignment %s", assignmentId));
+
+        // deleting config file
+        File file = Paths.get(
+            this.assignmentBasePath,
+            assignmentId + ".txt")
+            .toFile();
+        file.delete();
+        LOG.debug(String.format("Deleted file: %s", file.getAbsolutePath()));
+
+        // deleting local copy of repository
+        file = Paths.get(
+            this.assignmentBasePath,
+            assignmentId).toFile();
+        FileSystemUtils.deleteRecursively(file);
+        LOG.debug(String.format("Deleted directory: %s", file.getAbsolutePath()));
+
+        LOG.info(String.format("assignment %s deletion complete", assignmentId));
+    }
+}
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java
index 0408c09..8bd3208 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java
@@ -19,6 +19,7 @@ public class ArchiveUtil
 
             ZipEntry entry;
             while ((entry = stream.getNextEntry()) != null) {
+            LOG.debug(String.format("Processing zip entry: %s", entry.getName()));
                 File file = outDir.resolve(entry.getName()).toFile();
                 LOG.debug(String.format("processing entry %s", file.getAbsolutePath()));
 
@@ -28,6 +29,7 @@ public class ArchiveUtil
 
                     LOG.debug("creating new file");
                     file.createNewFile();
+                    LOG.debug(String.format("Created new file: %s", file.getAbsolutePath()));
 
                     byte[] buffer = new byte[2048];
                     try (FileOutputStream fos = new FileOutputStream(file);
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 3f1b8dc..1fb3861 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -67,6 +67,16 @@ public class ExecuteTestUtil  {
             Paths.get(assignmentBasePath, assignmentId),
             testPath);
 
+		LOG.debug("copying exercise manifest from: %s in testPath: %s",
+		assignmentBasePath + assignmentId + "_checkout",
+		testPath.toString() );
+		Files.copy(Paths.get(
+			assignmentBasePath, assignmentId + "_checkout",
+			CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME),
+		 Paths.get(
+			testPath.toString(), 
+			CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME));
+
         LOG.debug("copy test config");
         Files.copy(
             Paths.get(assignmentBasePath, assignmentId + ".txt"),
@@ -140,14 +150,25 @@ public class ExecuteTestUtil  {
         
         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.debug(String.format(
+			"Reading Test Competency Profiles: basePath=%s, fileName=%s",
+			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, true));
         	
         	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
         	//testPathHost or assignmentBasePath
-        	Path exerciseManifestFile = Paths.get(assignmentBasePath, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+        	Path exerciseManifestFile = Paths.get(testPathHost.toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+			LOG.debug(String.format(
+				"Constructing Path for exercise manifest: testPath=%s, fileName=%s -> Resulting Path=%s",
+				testPathHost.toString(),
+				CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME,
+				exerciseManifestFile.toString()
+			));
 			if (Files.exists(exerciseManifestFile)) {
 				LOG.info("Found optional exercise competency profiles, generating recommendations...");
 				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java
index edf8324..f185b9c 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java
@@ -24,10 +24,12 @@ public class FileUtil {
                     deleteFolderRecursively(f);
                 } else {
                     f.delete();
+                System.out.println(String.format("Deleted file: %s", f.getAbsolutePath()));
                 }
             }
         }
         folder.delete();
+        System.out.println(String.format("Deleted folder: %s", folder.getAbsolutePath()));
     }
 
     public static void copyFolder(Path src, Path dst) throws IOException {
@@ -35,6 +37,7 @@ public class FileUtil {
             .forEach(source -> {
                 try {
                     Files.copy(source, dst.resolve(src.relativize(source)));
+                    System.out.println(String.format("Copying file from: %s to %s", source.toString(), dst.resolve(src.relativize(source)).toString()));
                 } catch (IOException e) {
                     throw new RuntimeException(e.getMessage(), e);
                 }
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java
index f2c8587..334bcd7 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java
@@ -104,7 +104,7 @@ public class RepoUtil {
             	//copy appropriate path from checkout directory to target directory
             	FileSystemUtils.copyRecursively(new File(checkoutDirectory+subDir), new File(targetPath));
             }
-            LOG.debug(String.format("cloned from %s to %s", config.group(1), targetDirectory));            
+            LOG.debug(String.format("cloned from %s via %s to %s", config.group(1), checkoutDirectory+subDir, targetDirectory));            
         }
         catch (IOException e) {
         	LOG.error(String.format("Error while cloning from %s: could not copy to unit test dir", config.group(1)), e);
-- 
GitLab


From c188404598d85eea11f64b9b458228d91a24b6fb Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Wed, 13 Nov 2024 17:56:41 +0100
Subject: [PATCH 5/8] Refactor for backtracking manifesto

---
 .../utils/CompetencyAssessmentUtil.java       | 284 ++++++++------
 .../dtabackend/utils/ExecuteTestUtil.java     | 345 ++++++++----------
 2 files changed, 321 insertions(+), 308 deletions(-)

diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
index cdc3b43..4fcd484 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
@@ -3,134 +3,188 @@ 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.Files;
 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 de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
 import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 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 String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME="exercise-tests.mft";
-	
-	/*public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException {
-		ResultSummary summary=ExecuteTestUtil.generateResult("1", Path.of(args[0]), Path.of(args[1]));
-        System.out.println(summary.successfulTestCompetencyProfile);
-	}
-	*/
-	public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
-		float[] tcpTotalProfile=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-		for(TestCompetencyProfile currentProfile: testCompetencyProfiles) {
-			tcpTotalProfile=ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
-		}
-		return tcpTotalProfile;
-	}
-	
-	public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
-		float[] sumSuccessful=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-		for(Result currentResult: resultSummary.results) {
-			boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
-	        if (isSuccess == success) {
-				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=ICompetencyProfile.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(ICompetencyProfile.COMPETENCY_SEPARATOR);
-				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
-				currentProfile.testPackageName=testEntyComponents[0];
-				currentProfile.testClassName=testEntyComponents[1];
-				currentProfile.testName=testEntyComponents[2];
-				for(int competencyIndex=0; competencyIndex<ICompetencyProfile.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 List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
-	    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
-
-	    try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(new File(exercisePath.toFile(), fileName)))) {
-	        String exerciseEntry = exerciseCompetencyManifest.readLine();
-
-	        while (exerciseEntry != null) {
-	            String[] exerciseEntyComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
-	            ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
-
-	            currentProfile.exerciseTopicName = exerciseEntyComponents[0];
-	            currentProfile.exerciseName = exerciseEntyComponents[1];
-	            currentProfile.exerciseURL = exerciseEntyComponents[2];
-	            
-	            for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-	                currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntyComponents[competencyIndex+3]);
-	            }
-	            
-	            currentProfile.difficulty = Float.parseFloat(exerciseEntyComponents[19]);
-
-	            exerciseCompetencyProfiles.add(currentProfile);
-	            
-	            exerciseEntry = exerciseCompetencyManifest.readLine();
-	        }
-	        exerciseCompetencyManifest.close();
-	        LOG.info("Added " + exerciseCompetencyProfiles.size() + " test competency profiles from exercise competency manifest.");
-	    } catch (FileNotFoundException e) {
-	        LOG.info("Exercise competency manifest file not found.");
-	    } catch (IOException e) {
-	        LOG.info("Exercise competency manifest file unreadable.");
-	    }
-
-	    return exerciseCompetencyProfiles;
-	}
-
-	public static String packFloats(float[] array) {
-		return IntStream.range(0, array.length)
-	            .mapToObj(i -> String.valueOf(array[i]))
-	            .collect(Collectors.joining(";"));
-	}
+    private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
+
+    public static final String TEST_COMPETENCY_MANIFEST_FILE_NAME = "competency-tests.mft";
+    public static final String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME = "exercise-tests.mft";
+
+    /**
+     * Retrieves the base directory where the test competency manifest is located
+     * by traversing upwards until it finds the file "exercise-tests.mft".
+     * 
+     * @param startDir Starting directory path to begin the search
+     * @return Path of the base directory if found; otherwise, null
+     */
+    public static Path getBaseDirectory(Path startDir) {
+        Path currentDir = startDir;
+        while (currentDir != null) {
+            Path manifestPath = currentDir.resolve(EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+            if (Files.exists(manifestPath)) {
+                return currentDir;
+            }
+            currentDir = currentDir.getParent(); 
+        }
+        LOG.warn("Base directory with " + EXERCISE_COMPETENCY_MANIFEST_FILE_NAME + " not found starting from " + startDir);
+        return null;
+    }
+
+    /**
+     * Reads and loads test competency profiles from the base directory.
+     *
+     * @param exercisePath Path to the starting exercise directory
+     * @return List of TestCompetencyProfile instances loaded from the manifest
+     */
+    public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path exercisePath) {
+        Path baseDir = getBaseDirectory(exercisePath);
+        if (baseDir == null) {
+            LOG.error("Unable to locate the base directory for reading test competency profiles.");
+            return null;
+        }
+        return readTestCompetencyProfiles(baseDir, TEST_COMPETENCY_MANIFEST_FILE_NAME);
+    }
+
+    /**
+     * Reads and loads exercise competency profiles from the specified exercise directory.
+     *
+     * @param exercisePath Path to the exercise directory
+     * @return List of ExerciseCompetencyProfile instances loaded from the manifest
+     */
+    public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath) {
+        return readExerciseCompetencyProfiles(exercisePath, EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+    }
+
+    private static List<TestCompetencyProfile> readTestCompetencyProfiles(Path baseDir, String fileName) {
+        List<TestCompetencyProfile> testCompetencyProfiles = new ArrayList<>();
+        Path manifestPath = baseDir.resolve(fileName);
+
+        try (BufferedReader testCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
+            String testEntry = testCompetencyManifest.readLine();
+            while (testEntry != null) {
+                String[] testEntryComponents = testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR);
+                TestCompetencyProfile currentProfile = new TestCompetencyProfile();
+                currentProfile.testPackageName = testEntryComponents[0];
+                currentProfile.testClassName = testEntryComponents[1];
+                currentProfile.testName = testEntryComponents[2];
+                for (int competencyIndex = 0; competencyIndex < ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(testEntryComponents[competencyIndex + 3]);
+                }
+                testCompetencyProfiles.add(currentProfile);
+                testEntry = testCompetencyManifest.readLine();
+            }
+            LOG.info("Loaded " + testCompetencyProfiles.size() + " test competency profiles from " + manifestPath);
+        } catch (IOException e) {
+            LOG.error("Error reading test competency manifest file at " + manifestPath, e);
+        }
+
+        return testCompetencyProfiles;
+    }
+
+    private static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
+        List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
+        Path manifestPath = exercisePath.resolve(fileName);
+
+        try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
+            String exerciseEntry = exerciseCompetencyManifest.readLine();
+            while (exerciseEntry != null) {
+                String[] exerciseEntryComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
+                ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
+                currentProfile.exerciseTopicName = exerciseEntryComponents[0];
+                currentProfile.exerciseName = exerciseEntryComponents[1];
+                currentProfile.exerciseURL = exerciseEntryComponents[2];
+                for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntryComponents[competencyIndex + 3]);
+                }
+                currentProfile.difficulty = Float.parseFloat(exerciseEntryComponents[19]);
+                exerciseCompetencyProfiles.add(currentProfile);
+                exerciseEntry = exerciseCompetencyManifest.readLine();
+            }
+            LOG.info("Loaded " + exerciseCompetencyProfiles.size() + " exercise competency profiles from " + manifestPath);
+        } catch (IOException e) {
+            LOG.error("Error reading exercise competency manifest file at " + manifestPath, e);
+        }
+
+        return exerciseCompetencyProfiles;
+    }
+
+    /**
+     * Converts an array of floats into a semicolon-separated string.
+     *
+     * @param array Array of float values to pack
+     * @return Semicolon-separated string of float values
+     */
+    public static String packFloats(float[] array) {
+        return IntStream.range(0, array.length)
+                .mapToObj(i -> String.valueOf(array[i]))
+                .collect(Collectors.joining(";"));
+    }
+
+    /**
+     * Sums the competency profiles across all test competency profiles.
+     *
+     * @param testCompetencyProfiles List of test competency profiles
+     * @return An array representing the sum of competencies
+     */
+    public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
+        float[] tcpTotalProfile = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+        for (TestCompetencyProfile currentProfile : testCompetencyProfiles) {
+            tcpTotalProfile = ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
+        }
+        return tcpTotalProfile;
+    }
+
+    /**
+     * Sums only the successful test competency profiles, based on the results summary.
+     *
+     * @param testCompetencyProfiles List of test competency profiles
+     * @param resultSummary The result summary containing test results
+     * @param success Indicates whether to sum successful (true) or unsuccessful (false) profiles
+     * @return An array representing the sum of competencies for successful or unsuccessful profiles
+     */
+    public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
+        float[] sumSuccessful = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+        for (Result currentResult : resultSummary.results) {
+            boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
+            if (isSuccess == success) {
+                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 = ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
+                }
+            }
+        }
+        return sumSuccessful;
+    }
 
+    /**
+     * Checks if all elements in the competency profile array are zero (indicating full success).
+     *
+     * @param competencyProfile Array of competency values
+     * @return True if all values are zero; false otherwise
+     */
+    public static boolean isFullSuccess(float[] competencyProfile) {
+        for (float value : competencyProfile) {
+            if (value != 0.0f) {
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 333b177..73e5ced 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -7,7 +7,6 @@ import com.github.dockerjava.api.model.Bind;
 import com.github.dockerjava.api.model.Volume;
 
 import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
-import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 import de.hftstuttgart.dtabackend.models.Recommendation;
 import de.hftstuttgart.dtabackend.models.ResultSummary;
 import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
@@ -22,10 +21,7 @@ import java.net.MalformedURLException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -38,217 +34,180 @@ public class ExecuteTestUtil  {
     private final String assignmentBasePath;
     private final Path testTmpPathHost;
 
-    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(
+        this.assignmentBasePath = Paths.get(
             env.getProperty("data.dir"),
-            env.getProperty("data.dir.test.folder.name"));
-        this.assignmentBasePath = p.toAbsolutePath().toString();
+            env.getProperty("data.dir.test.folder.name"))
+            .toAbsolutePath().toString();
 
-        // set path of temporary directory on host _and_ inside our container, _must_ be identical 
         this.testTmpPathHost = Paths.get(env.getProperty("host.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"));
-
+        // Define paths
+        Path testPath = workDirectory.resolve("test");
+        Path srcPath = workDirectory.resolve("src");
+        Path resultPath = workDirectory.resolve("result");
+
+        // Clone test to temporary directory
+        LOG.debug("Copying pre-downloaded unittest repo");
+        FileUtil.copyFolder(Paths.get(assignmentBasePath, assignmentId), testPath);
+
+        // Copy configuration file
+        LOG.debug("Copy test config");
+        Files.copy(Paths.get(assignmentBasePath, assignmentId + ".txt"), workDirectory.resolve("config.txt"));
         Files.createDirectory(resultPath);
 
-        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="";
-        if(config==null)
-        {
-        	config = RegexUtil.extractConfig(
-                    new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt").toFile()), Pattern.compile(RegexUtil.TESTCONFIGREGEX));
-        	if(config==null)
-        	{
-           		throw new RuntimeException("couldn't find repo config for unittest image extraction");
-        	}
-        	image=config.group(4);
-        }
-        else
-        {
-        	image=config.group(5);
-        }
-        // 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()
-        );
+        // Load container image configuration
+        LOG.info("Reading test config");
+        String image = loadImageConfig(workDirectory);
+
+        // Define paths to mount in the container
+        Path testPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(testPath.getFileName());
+        Path srcPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(srcPath.getFileName());
+        Path resultPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(resultPath.getFileName());
 
-        // start test-container with professor given image and bind mounts for test, submission and result
+        // Start container with mounts
         dockerUtil.runContainer(
             image,
-            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"))
+            new Bind(testPathHost.toString(), new Volume("/data/test")),
+            new Bind(srcPathHost.toString(), new Volume("/data/src")),
+            new Bind(resultPathHost.toString(), new Volume("/data/result"))
         );
 
-        ResultSummary resultSummary = generateResult(assignmentId, resultPath, testPathHost);
-        
-        return resultSummary;
+        return generateResult(assignmentId, resultPath, testPathHost);
+    }
+
+    private String loadImageConfig(Path workDirectory) throws FileNotFoundException {
+        Matcher config = RegexUtil.extractConfig(
+            new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
+            Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX));
+
+        if (config == null) {
+            config = RegexUtil.extractConfig(
+                new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
+                Pattern.compile(RegexUtil.TESTCONFIGREGEX));
+            
+            if (config == null) {
+                throw new RuntimeException("Couldn't find repo config for unittest image extraction");
+            }
+            return config.group(4);
+        }
+        return config.group(5);
     }
 
-	private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
-			throws IOException, StreamReadException, DatabindException, MalformedURLException {
-		// define expected result file
-        File resultFile = Paths.get(resultPath.toAbsolutePath().toString(), "result.json").toFile();
+    private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
+            throws IOException, StreamReadException, DatabindException, MalformedURLException {
+        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()));
-            throw new RuntimeException("no resultfile found");
+            throw new RuntimeException("No result file found");
         }
 
-        LOG.debug("parse results json");
+        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.debug("Result JSON returned at " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results.");
+
+        LOG.info("Checking for optional test competency profile information...");
+        List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost);
+
+        if (testCompetencyProfiles != null) {
+            LOG.info("Found test competency profiles, generating profile data...");
+            resultSummary.overallTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles));
+            resultSummary.successfulTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true));
+            
+            Path exerciseManifestFile = testPathHost.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+            if (Files.exists(exerciseManifestFile)) {
+                LOG.info("Found exercise competency profiles, generating recommendations...");
+                resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
+            }
+        }
+        return resultSummary;
+    }
+
+    public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
+        throws FileNotFoundException {
+    
+    String testRepoURL = RegexUtil.extractConfig(
+        new FileInputStream(Paths.get(assignmentBasePath, assignmentId + ".txt").toFile()), 
+        Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)).group(1);
+
+    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost);
+
+    int currentTopicIndex = 0;
+    float currentDifficulty = 0.0f;
+    Map<String, Integer> topicOrder = new HashMap<>();
+    int order = 1;
+
+    // Determine currentTopicIndex and set currentDifficulty based on exercise profiles
+    for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
+        if (!topicOrder.containsKey(e.exerciseTopicName)) {
+            topicOrder.put(e.exerciseTopicName, order++);
+        }
+        if (e.exerciseURL.equals(testRepoURL)) {
+            currentTopicIndex = order;
+            currentDifficulty = e.difficulty;  // Directly assign to currentDifficulty
+        }
+    }
+
+    // Sum competencies for unsuccessful tests
+    float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
+
+    // Filter exercises based on topics and difficulty
+    List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
+            exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, currentDifficulty, unsuccessful);
+
+    // Generate recommendations without using lambda expressions
+    List<Recommendation> recommendedExercises = new ArrayList<>();
+    for (ExerciseCompetencyProfile profile : filteredExercises) {
+        float score = calculateScore(profile, unsuccessful, topicOrder, currentDifficulty);
+        Recommendation recommendation = new Recommendation(
+                profile.exerciseTopicName, 
+                profile.exerciseURL, 
+                profile.exerciseName, 
+                profile.difficulty, 
+                score);
+        recommendedExercises.add(recommendation);
+    }
+
+    // Sort the recommendations using Collections.sort and a comparator
+    Collections.sort(recommendedExercises, Recommendation.COMPARE_BY_SCORE);
+    
+    return recommendedExercises;
+}
+
+    public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> profiles,
+            Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful) {
         
-        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, true));
-        	
-        	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
-        	//testPathHost or assignmentBasePath
-        	Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-			if (Files.exists(exerciseManifestFile)) {
-				LOG.info("Found optional exercise competency profiles, generating recommendations...");
-				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
-			}
+        List<ExerciseCompetencyProfile> filteredExercises = profiles.stream()
+                .filter(profile -> topicOrder.get(profile.exerciseTopicName) <= currentTopicIndex)
+                .collect(Collectors.toList());
+
+        if (CompetencyAssessmentUtil.isFullSuccess(unsuccessful)) {
+            filteredExercises = filteredExercises.stream()
+                    .filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL))
+                    .collect(Collectors.toList());
+        } else {
+            filteredExercises = filteredExercises.stream()
+                    .filter(profile -> profile.difficulty <= currentDifficulty)
+                    .collect(Collectors.toList());
         }
-		return resultSummary;
-	}
-	
-	/*
-	 * exercise recommendation part
-	 */
-	public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, 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);
-
-		List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-
-		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;
-			}
-		}
-		
-		//filter exercises according to success
-		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) {
-			Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName,
-					exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty));
-			recommendedExercises.add(recommendation);
-			LOG.info("Recommending exercise "+recommendation.topic+"/"+recommendation.exerciseName+" with score "+recommendation.score);
-		}
-		//sort the recommendations for successful or resilient learners, otherwise reverse in display
-		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, 
-			ResultSummary resultSummary) {
-		//filter out all advanced topics in any case
-		//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());
-
-		//filter by difficulty according to success
-		if (isFullSuccess(unsuccessful)) {
-			filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList());
-		} 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 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;
-		//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)));
-		
-		//topic component
-		score *= topicOrder.get(exerciseProfile.exerciseTopicName);
-
-		score = Math.round(score * 10.0f) / 10.0f;
-		return score;
-	}
+
+        return filteredExercises;
+    }
+
+    public static float calculateScore(ExerciseCompetencyProfile profile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) {
+        float score = 1.0f;
+        for (int i = 0; i < profile.competencyAssessments.length - 1; i++) {
+            score += profile.competencyAssessments[i] * unsuccessful[i];
+        }
+
+        score *= profile.difficulty * (0.5f + Math.abs(currentDifficulty - profile.difficulty));
+        score *= topicOrder.getOrDefault(profile.exerciseTopicName, 1);
+        
+        return Math.round(score * 10.0f) / 10.0f;
+    }
 }
\ No newline at end of file
-- 
GitLab


From 73c101298424038d18285088d317a368b3dd97e5 Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Tue, 19 Nov 2024 14:38:40 +0100
Subject: [PATCH 6/8] Revert "Refactor for backtracking manifesto"

This reverts commit 375b37bf188ffd26fdd0bf0d7aeab0170b63ea32.
---
 .../utils/CompetencyAssessmentUtil.java       | 284 ++++++--------
 .../dtabackend/utils/ExecuteTestUtil.java     | 345 ++++++++++--------
 2 files changed, 308 insertions(+), 321 deletions(-)

diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
index 4fcd484..cdc3b43 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/CompetencyAssessmentUtil.java
@@ -3,188 +3,134 @@ package de.hftstuttgart.dtabackend.utils;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.IOException;
-import java.nio.file.Files;
+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 de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
 import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 import de.hftstuttgart.dtabackend.models.Result;
 import de.hftstuttgart.dtabackend.models.ResultSummary;
 import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
 
-public class CompetencyAssessmentUtil {
-    private static final Logger LOG = LogManager.getLogger(CompetencyAssessmentUtil.class);
-
-    public static final String TEST_COMPETENCY_MANIFEST_FILE_NAME = "competency-tests.mft";
-    public static final String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME = "exercise-tests.mft";
-
-    /**
-     * Retrieves the base directory where the test competency manifest is located
-     * by traversing upwards until it finds the file "exercise-tests.mft".
-     * 
-     * @param startDir Starting directory path to begin the search
-     * @return Path of the base directory if found; otherwise, null
-     */
-    public static Path getBaseDirectory(Path startDir) {
-        Path currentDir = startDir;
-        while (currentDir != null) {
-            Path manifestPath = currentDir.resolve(EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-            if (Files.exists(manifestPath)) {
-                return currentDir;
-            }
-            currentDir = currentDir.getParent(); 
-        }
-        LOG.warn("Base directory with " + EXERCISE_COMPETENCY_MANIFEST_FILE_NAME + " not found starting from " + startDir);
-        return null;
-    }
-
-    /**
-     * Reads and loads test competency profiles from the base directory.
-     *
-     * @param exercisePath Path to the starting exercise directory
-     * @return List of TestCompetencyProfile instances loaded from the manifest
-     */
-    public static List<TestCompetencyProfile> readTestCompetencyProfiles(Path exercisePath) {
-        Path baseDir = getBaseDirectory(exercisePath);
-        if (baseDir == null) {
-            LOG.error("Unable to locate the base directory for reading test competency profiles.");
-            return null;
-        }
-        return readTestCompetencyProfiles(baseDir, TEST_COMPETENCY_MANIFEST_FILE_NAME);
-    }
-
-    /**
-     * Reads and loads exercise competency profiles from the specified exercise directory.
-     *
-     * @param exercisePath Path to the exercise directory
-     * @return List of ExerciseCompetencyProfile instances loaded from the manifest
-     */
-    public static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath) {
-        return readExerciseCompetencyProfiles(exercisePath, EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-    }
-
-    private static List<TestCompetencyProfile> readTestCompetencyProfiles(Path baseDir, String fileName) {
-        List<TestCompetencyProfile> testCompetencyProfiles = new ArrayList<>();
-        Path manifestPath = baseDir.resolve(fileName);
+import java.io.FileNotFoundException;
 
-        try (BufferedReader testCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
-            String testEntry = testCompetencyManifest.readLine();
-            while (testEntry != null) {
-                String[] testEntryComponents = testEntry.split(ICompetencyProfile.COMPETENCY_SEPARATOR);
-                TestCompetencyProfile currentProfile = new TestCompetencyProfile();
-                currentProfile.testPackageName = testEntryComponents[0];
-                currentProfile.testClassName = testEntryComponents[1];
-                currentProfile.testName = testEntryComponents[2];
-                for (int competencyIndex = 0; competencyIndex < ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(testEntryComponents[competencyIndex + 3]);
-                }
-                testCompetencyProfiles.add(currentProfile);
-                testEntry = testCompetencyManifest.readLine();
-            }
-            LOG.info("Loaded " + testCompetencyProfiles.size() + " test competency profiles from " + manifestPath);
-        } catch (IOException e) {
-            LOG.error("Error reading test competency manifest file at " + manifestPath, e);
-        }
-
-        return testCompetencyProfiles;
-    }
-
-    private static List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
-        List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
-        Path manifestPath = exercisePath.resolve(fileName);
-
-        try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(manifestPath.toFile()))) {
-            String exerciseEntry = exerciseCompetencyManifest.readLine();
-            while (exerciseEntry != null) {
-                String[] exerciseEntryComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
-                ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
-                currentProfile.exerciseTopicName = exerciseEntryComponents[0];
-                currentProfile.exerciseName = exerciseEntryComponents[1];
-                currentProfile.exerciseURL = exerciseEntryComponents[2];
-                for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
-                    currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntryComponents[competencyIndex + 3]);
-                }
-                currentProfile.difficulty = Float.parseFloat(exerciseEntryComponents[19]);
-                exerciseCompetencyProfiles.add(currentProfile);
-                exerciseEntry = exerciseCompetencyManifest.readLine();
-            }
-            LOG.info("Loaded " + exerciseCompetencyProfiles.size() + " exercise competency profiles from " + manifestPath);
-        } catch (IOException e) {
-            LOG.error("Error reading exercise competency manifest file at " + manifestPath, e);
-        }
-
-        return exerciseCompetencyProfiles;
-    }
-
-    /**
-     * Converts an array of floats into a semicolon-separated string.
-     *
-     * @param array Array of float values to pack
-     * @return Semicolon-separated string of float values
-     */
-    public static String packFloats(float[] array) {
-        return IntStream.range(0, array.length)
-                .mapToObj(i -> String.valueOf(array[i]))
-                .collect(Collectors.joining(";"));
-    }
-
-    /**
-     * Sums the competency profiles across all test competency profiles.
-     *
-     * @param testCompetencyProfiles List of test competency profiles
-     * @return An array representing the sum of competencies
-     */
-    public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
-        float[] tcpTotalProfile = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-        for (TestCompetencyProfile currentProfile : testCompetencyProfiles) {
-            tcpTotalProfile = ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
-        }
-        return tcpTotalProfile;
-    }
-
-    /**
-     * Sums only the successful test competency profiles, based on the results summary.
-     *
-     * @param testCompetencyProfiles List of test competency profiles
-     * @param resultSummary The result summary containing test results
-     * @param success Indicates whether to sum successful (true) or unsuccessful (false) profiles
-     * @return An array representing the sum of competencies for successful or unsuccessful profiles
-     */
-    public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
-        float[] sumSuccessful = new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
-        for (Result currentResult : resultSummary.results) {
-            boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
-            if (isSuccess == success) {
-                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 = ICompetencyProfile.competencySum(sumSuccessful, testCompetencyProfiles.get(testIndex).competencyAssessments);
-                }
-            }
-        }
-        return sumSuccessful;
-    }
+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 String EXERCISE_COMPETENCY_MANIFEST_FILE_NAME="exercise-tests.mft";
+	
+	/*public static void main(String[] args) throws StreamReadException, DatabindException, MalformedURLException, IOException {
+		ResultSummary summary=ExecuteTestUtil.generateResult("1", Path.of(args[0]), Path.of(args[1]));
+        System.out.println(summary.successfulTestCompetencyProfile);
+	}
+	*/
+	public static float[] sumTestCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles) {
+		float[] tcpTotalProfile=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+		for(TestCompetencyProfile currentProfile: testCompetencyProfiles) {
+			tcpTotalProfile=ICompetencyProfile.competencySum(tcpTotalProfile, currentProfile.competencyAssessments);
+		}
+		return tcpTotalProfile;
+	}
+	
+	public static float[] sumSuccessfulCompetencyProfiles(List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary, boolean success) {
+		float[] sumSuccessful=new float[ICompetencyProfile.MAX_COMPETENCY_DIMENSIONS];
+		for(Result currentResult: resultSummary.results) {
+			boolean isSuccess = Integer.valueOf(currentResult.state).equals(Result.State.SUCCESS.ordinal());
+	        if (isSuccess == success) {
+				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=ICompetencyProfile.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(ICompetencyProfile.COMPETENCY_SEPARATOR);
+				TestCompetencyProfile currentProfile=new TestCompetencyProfile();
+				currentProfile.testPackageName=testEntyComponents[0];
+				currentProfile.testClassName=testEntyComponents[1];
+				currentProfile.testName=testEntyComponents[2];
+				for(int competencyIndex=0; competencyIndex<ICompetencyProfile.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 List<ExerciseCompetencyProfile> readExerciseCompetencyProfiles(Path exercisePath, String fileName) {
+	    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = new ArrayList<>();
+
+	    try (BufferedReader exerciseCompetencyManifest = new BufferedReader(new FileReader(new File(exercisePath.toFile(), fileName)))) {
+	        String exerciseEntry = exerciseCompetencyManifest.readLine();
+
+	        while (exerciseEntry != null) {
+	            String[] exerciseEntyComponents = exerciseEntry.split(ExerciseCompetencyProfile.COMPETENCY_SEPARATOR);
+	            ExerciseCompetencyProfile currentProfile = new ExerciseCompetencyProfile();
+
+	            currentProfile.exerciseTopicName = exerciseEntyComponents[0];
+	            currentProfile.exerciseName = exerciseEntyComponents[1];
+	            currentProfile.exerciseURL = exerciseEntyComponents[2];
+	            
+	            for (int competencyIndex = 0; competencyIndex < ExerciseCompetencyProfile.MAX_COMPETENCY_DIMENSIONS; competencyIndex++) {
+	                currentProfile.competencyAssessments[competencyIndex] = Float.valueOf(exerciseEntyComponents[competencyIndex+3]);
+	            }
+	            
+	            currentProfile.difficulty = Float.parseFloat(exerciseEntyComponents[19]);
+
+	            exerciseCompetencyProfiles.add(currentProfile);
+	            
+	            exerciseEntry = exerciseCompetencyManifest.readLine();
+	        }
+	        exerciseCompetencyManifest.close();
+	        LOG.info("Added " + exerciseCompetencyProfiles.size() + " test competency profiles from exercise competency manifest.");
+	    } catch (FileNotFoundException e) {
+	        LOG.info("Exercise competency manifest file not found.");
+	    } catch (IOException e) {
+	        LOG.info("Exercise competency manifest file unreadable.");
+	    }
+
+	    return exerciseCompetencyProfiles;
+	}
+
+	public static String packFloats(float[] array) {
+		return IntStream.range(0, array.length)
+	            .mapToObj(i -> String.valueOf(array[i]))
+	            .collect(Collectors.joining(";"));
+	}
 
-    /**
-     * Checks if all elements in the competency profile array are zero (indicating full success).
-     *
-     * @param competencyProfile Array of competency values
-     * @return True if all values are zero; false otherwise
-     */
-    public static boolean isFullSuccess(float[] competencyProfile) {
-        for (float value : competencyProfile) {
-            if (value != 0.0f) {
-                return false;
-            }
-        }
-        return true;
-    }
 }
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 73e5ced..333b177 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -7,6 +7,7 @@ import com.github.dockerjava.api.model.Bind;
 import com.github.dockerjava.api.model.Volume;
 
 import de.hftstuttgart.dtabackend.models.ExerciseCompetencyProfile;
+import de.hftstuttgart.dtabackend.models.ICompetencyProfile;
 import de.hftstuttgart.dtabackend.models.Recommendation;
 import de.hftstuttgart.dtabackend.models.ResultSummary;
 import de.hftstuttgart.dtabackend.models.TestCompetencyProfile;
@@ -21,7 +22,10 @@ import java.net.MalformedURLException;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -34,180 +38,217 @@ public class ExecuteTestUtil  {
     private final String assignmentBasePath;
     private final Path testTmpPathHost;
 
-    public ExecuteTestUtil(Environment env, DockerUtil dockerUtil) {
+    public ExecuteTestUtil(
+        Environment env,
+        DockerUtil dockerUtil
+    ) {
         this.dockerUtil = dockerUtil;
-        this.assignmentBasePath = Paths.get(
+
+        // set base path for assignments to be stored
+        Path p = Paths.get(
             env.getProperty("data.dir"),
-            env.getProperty("data.dir.test.folder.name"))
-            .toAbsolutePath().toString();
+            env.getProperty("data.dir.test.folder.name"));
+        this.assignmentBasePath = p.toAbsolutePath().toString();
 
+        // set path of temporary directory on host _and_ inside our container, _must_ be identical 
         this.testTmpPathHost = Paths.get(env.getProperty("host.tests.tmp.dir"));
     }
 
     public ResultSummary runTests(String assignmentId, Path workDirectory) throws IOException, InterruptedException {
-        // Define paths
-        Path testPath = workDirectory.resolve("test");
-        Path srcPath = workDirectory.resolve("src");
-        Path resultPath = workDirectory.resolve("result");
-
-        // Clone test to temporary directory
-        LOG.debug("Copying pre-downloaded unittest repo");
-        FileUtil.copyFolder(Paths.get(assignmentBasePath, assignmentId), testPath);
-
-        // Copy configuration file
-        LOG.debug("Copy test config");
-        Files.copy(Paths.get(assignmentBasePath, assignmentId + ".txt"), workDirectory.resolve("config.txt"));
-        Files.createDirectory(resultPath);
 
-        // Load container image configuration
-        LOG.info("Reading test config");
-        String image = loadImageConfig(workDirectory);
+        // 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");
 
-        // Define paths to mount in the container
-        Path testPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(testPath.getFileName());
-        Path srcPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(srcPath.getFileName());
-        Path resultPathHost = testTmpPathHost.resolve(workDirectory.getFileName()).resolve(resultPath.getFileName());
+        // clone stored test to tmpdir
+        LOG.debug("copying pre-downloaded unitttest repo");
+        FileUtil.copyFolder(
+            Paths.get(assignmentBasePath, assignmentId),
+            testPath);
 
-        // Start container with mounts
-        dockerUtil.runContainer(
-            image,
-            new Bind(testPathHost.toString(), new Volume("/data/test")),
-            new Bind(srcPathHost.toString(), new Volume("/data/src")),
-            new Bind(resultPathHost.toString(), new Volume("/data/result"))
-        );
+        LOG.debug("copy test config");
+        Files.copy(
+            Paths.get(assignmentBasePath, assignmentId + ".txt"),
+            Paths.get(workDirectory.toAbsolutePath().toString(), "config.txt"));
 
-        return generateResult(assignmentId, resultPath, testPathHost);
-    }
+        Files.createDirectory(resultPath);
 
-    private String loadImageConfig(Path workDirectory) throws FileNotFoundException {
+        LOG.info("reading test config");
         Matcher config = RegexUtil.extractConfig(
-            new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
-            Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX));
-
-        if (config == null) {
-            config = RegexUtil.extractConfig(
-                new FileInputStream(workDirectory.resolve("config.txt").toFile()), 
-                Pattern.compile(RegexUtil.TESTCONFIGREGEX));
-            
-            if (config == null) {
-                throw new RuntimeException("Couldn't find repo config for unittest image extraction");
-            }
-            return config.group(4);
+            new FileInputStream(Paths.get(workDirectory.toAbsolutePath().toString(), "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));
+        	if(config==null)
+        	{
+           		throw new RuntimeException("couldn't find repo config for unittest image extraction");
+        	}
+        	image=config.group(4);
+        }
+        else
+        {
+        	image=config.group(5);
         }
-        return config.group(5);
+        // 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(
+            image,
+            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(assignmentId, resultPath, testPathHost);
+        
+        return resultSummary;
     }
 
-    private ResultSummary generateResult(String assignmentId, Path resultPath, Path testPathHost)
-            throws IOException, StreamReadException, DatabindException, MalformedURLException {
-        File resultFile = resultPath.resolve("result.json").toFile();
+	private ResultSummary generateResult(String assignmentId, 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 result file found");
+            throw new RuntimeException("no resultfile found");
         }
 
-        LOG.debug("Parse results JSON");
+        LOG.debug("parse results json");
         ObjectMapper objectMapper = new ObjectMapper();
         ResultSummary resultSummary = objectMapper.readValue(resultFile.toURI().toURL(), ResultSummary.class);
-        LOG.debug("Result JSON returned at " + resultSummary.timestamp + " with " + resultSummary.results.size() + " test results.");
-
-        LOG.info("Checking for optional test competency profile information...");
-        List<TestCompetencyProfile> testCompetencyProfiles = CompetencyAssessmentUtil.readTestCompetencyProfiles(testPathHost);
-
-        if (testCompetencyProfiles != null) {
-            LOG.info("Found test competency profiles, generating profile data...");
-            resultSummary.overallTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumTestCompetencyProfiles(testCompetencyProfiles));
-            resultSummary.successfulTestCompetencyProfile = CompetencyAssessmentUtil.packFloats(CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, true));
-            
-            Path exerciseManifestFile = testPathHost.resolve(CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
-            if (Files.exists(exerciseManifestFile)) {
-                LOG.info("Found exercise competency profiles, generating recommendations...");
-                resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
-            }
-        }
-        return resultSummary;
-    }
-
-    public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, List<TestCompetencyProfile> testCompetencyProfiles, ResultSummary resultSummary)
-        throws FileNotFoundException {
-    
-    String testRepoURL = RegexUtil.extractConfig(
-        new FileInputStream(Paths.get(assignmentBasePath, assignmentId + ".txt").toFile()), 
-        Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX)).group(1);
-
-    List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost);
-
-    int currentTopicIndex = 0;
-    float currentDifficulty = 0.0f;
-    Map<String, Integer> topicOrder = new HashMap<>();
-    int order = 1;
-
-    // Determine currentTopicIndex and set currentDifficulty based on exercise profiles
-    for (ExerciseCompetencyProfile e : exerciseCompetencyProfiles) {
-        if (!topicOrder.containsKey(e.exerciseTopicName)) {
-            topicOrder.put(e.exerciseTopicName, order++);
-        }
-        if (e.exerciseURL.equals(testRepoURL)) {
-            currentTopicIndex = order;
-            currentDifficulty = e.difficulty;  // Directly assign to currentDifficulty
-        }
-    }
-
-    // Sum competencies for unsuccessful tests
-    float[] unsuccessful = CompetencyAssessmentUtil.sumSuccessfulCompetencyProfiles(testCompetencyProfiles, resultSummary, false);
-
-    // Filter exercises based on topics and difficulty
-    List<ExerciseCompetencyProfile> filteredExercises = filterExercisesByTopicsAndDifficulty(
-            exerciseCompetencyProfiles, topicOrder, currentTopicIndex, testRepoURL, currentDifficulty, unsuccessful);
-
-    // Generate recommendations without using lambda expressions
-    List<Recommendation> recommendedExercises = new ArrayList<>();
-    for (ExerciseCompetencyProfile profile : filteredExercises) {
-        float score = calculateScore(profile, unsuccessful, topicOrder, currentDifficulty);
-        Recommendation recommendation = new Recommendation(
-                profile.exerciseTopicName, 
-                profile.exerciseURL, 
-                profile.exerciseName, 
-                profile.difficulty, 
-                score);
-        recommendedExercises.add(recommendation);
-    }
-
-    // Sort the recommendations using Collections.sort and a comparator
-    Collections.sort(recommendedExercises, Recommendation.COMPARE_BY_SCORE);
-    
-    return recommendedExercises;
-}
-
-    public static List<ExerciseCompetencyProfile> filterExercisesByTopicsAndDifficulty(List<ExerciseCompetencyProfile> profiles,
-            Map<String, Integer> topicOrder, int currentTopicIndex, String testRepoURL, float currentDifficulty, float[] unsuccessful) {
+        LOG.debug("result json returned time "+ resultSummary.timestamp + " with "+resultSummary.results.size()+ " test results.");
         
-        List<ExerciseCompetencyProfile> filteredExercises = profiles.stream()
-                .filter(profile -> topicOrder.get(profile.exerciseTopicName) <= currentTopicIndex)
-                .collect(Collectors.toList());
-
-        if (CompetencyAssessmentUtil.isFullSuccess(unsuccessful)) {
-            filteredExercises = filteredExercises.stream()
-                    .filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL))
-                    .collect(Collectors.toList());
-        } else {
-            filteredExercises = filteredExercises.stream()
-                    .filter(profile -> profile.difficulty <= currentDifficulty)
-                    .collect(Collectors.toList());
+        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, true));
+        	
+        	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
+        	//testPathHost or assignmentBasePath
+        	Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+			if (Files.exists(exerciseManifestFile)) {
+				LOG.info("Found optional exercise competency profiles, generating recommendations...");
+				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
+			}
         }
-
-        return filteredExercises;
-    }
-
-    public static float calculateScore(ExerciseCompetencyProfile profile, float[] unsuccessful, Map<String, Integer> topicOrder, float currentDifficulty) {
-        float score = 1.0f;
-        for (int i = 0; i < profile.competencyAssessments.length - 1; i++) {
-            score += profile.competencyAssessments[i] * unsuccessful[i];
-        }
-
-        score *= profile.difficulty * (0.5f + Math.abs(currentDifficulty - profile.difficulty));
-        score *= topicOrder.getOrDefault(profile.exerciseTopicName, 1);
-        
-        return Math.round(score * 10.0f) / 10.0f;
-    }
+		return resultSummary;
+	}
+	
+	/*
+	 * exercise recommendation part
+	 */
+	public List<Recommendation> recommendNextExercises(String assignmentId, Path testPathHost, 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);
+
+		List<ExerciseCompetencyProfile> exerciseCompetencyProfiles = CompetencyAssessmentUtil.readExerciseCompetencyProfiles(testPathHost, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+
+		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;
+			}
+		}
+		
+		//filter exercises according to success
+		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) {
+			Recommendation recommendation = new Recommendation(exerciseProfile.exerciseTopicName, exerciseProfile.exerciseURL, exerciseProfile.exerciseName,
+					exerciseProfile.difficulty, calculateScore(exerciseProfile, unsuccessful, topicOrder, currentDifficulty));
+			recommendedExercises.add(recommendation);
+			LOG.info("Recommending exercise "+recommendation.topic+"/"+recommendation.exerciseName+" with score "+recommendation.score);
+		}
+		//sort the recommendations for successful or resilient learners, otherwise reverse in display
+		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, 
+			ResultSummary resultSummary) {
+		//filter out all advanced topics in any case
+		//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());
+
+		//filter by difficulty according to success
+		if (isFullSuccess(unsuccessful)) {
+			filteredExercises = filteredExercises.stream().filter(profile -> profile.difficulty >= currentDifficulty && !testRepoURL.equals(profile.exerciseURL)).collect(Collectors.toList());
+		} 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 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;
+		//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)));
+		
+		//topic component
+		score *= topicOrder.get(exerciseProfile.exerciseTopicName);
+
+		score = Math.round(score * 10.0f) / 10.0f;
+		return score;
+	}
 }
\ No newline at end of file
-- 
GitLab


From f8817cf608a34cc8d8a2c132f9e701f54ad4fca9 Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Tue, 19 Nov 2024 14:41:34 +0100
Subject: [PATCH 7/8] Replaced tetsPathHost to assignmentBasePath

---
 .../java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java  | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 333b177..3f1b8dc 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -147,7 +147,7 @@ public class ExecuteTestUtil  {
         	
         	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
         	//testPathHost or assignmentBasePath
-        	Path exerciseManifestFile = Paths.get(testPathHost.toAbsolutePath().toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+        	Path exerciseManifestFile = Paths.get(assignmentBasePath, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
 			if (Files.exists(exerciseManifestFile)) {
 				LOG.info("Found optional exercise competency profiles, generating recommendations...");
 				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
-- 
GitLab


From b8793634e87adca5bb1e8dd2a15470b4c7e5d43e Mon Sep 17 00:00:00 2001
From: mamunozgil <miguel.munoz-gil@hft-stuttgart.de>
Date: Thu, 21 Nov 2024 12:01:29 +0100
Subject: [PATCH 8/8] Added debug logs and exercise-manifest copy

---
 .gitignore                                    |   2 +
 .../dtabackend/rest/v1/task/TaskUpload.java   | 219 ++++++++---------
 .../rest/v1/unittest/UnitTestUpload.java      | 229 +++++++++---------
 .../dtabackend/utils/ArchiveUtil.java         |   2 +
 .../dtabackend/utils/ExecuteTestUtil.java     |  25 +-
 .../dtabackend/utils/FileUtil.java            |   3 +
 .../dtabackend/utils/RepoUtil.java            |   2 +-
 7 files changed, 257 insertions(+), 225 deletions(-)
 create mode 100644 .gitignore

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ac8dcdd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+/local-maven-repo
\ No newline at end of file
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 7b14090..2eec4e1 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,109 +1,110 @@
-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 java.util.regex.Pattern;
-
-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 RepoUtil repoUtil;
-    private final Path testTmpPath;
-    private final 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"));
-    }
-
-    @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, "dta");
-        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 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);
-                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 java.util.regex.Pattern;
+
+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 RepoUtil repoUtil;
+    private final Path testTmpPath;
+    private final 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"));
+    }
+
+    @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, "dta");
+        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");
+        LOG.debug(String.format("Source path defined as: %s", srcPath.toAbsolutePath().toString()));
+
+        String mimeInfo = new Tika().detect(taskFileRef.getInputStream());
+        switch (mimeInfo) {
+            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);
+                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/rest/v1/unittest/UnitTestUpload.java b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/unittest/UnitTestUpload.java
index 79cc5ed..924f982 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/rest/v1/unittest/UnitTestUpload.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/rest/v1/unittest/UnitTestUpload.java
@@ -1,113 +1,116 @@
-package de.hftstuttgart.dtabackend.rest.v1.unittest;
-
-import de.hftstuttgart.dtabackend.utils.RepoUtil;
-import de.hftstuttgart.dtabackend.utils.RegexUtil;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.springframework.core.env.Environment;
-import org.springframework.util.FileSystemUtils;
-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 jakarta.servlet.annotation.MultipartConfig;
-
-import java.io.*;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Rest controller for anything related to the TEST files.
- */
-@RestController
-@RequestMapping("/v1/unittest")
-@MultipartConfig
-public class UnitTestUpload {
-
-    private static final Logger LOG = LogManager.getLogger(UnitTestUpload.class);
-    private final RepoUtil repoUtil;
-    private final String assignmentBasePath;
-
-    public UnitTestUpload(Environment env, RepoUtil repoUtil) {
-        this.repoUtil = repoUtil;
-
-        Path p = Paths.get(env.getProperty("data.dir"), env.getProperty("data.dir.test.folder.name"));
-        this.assignmentBasePath = p.toAbsolutePath().toString();
-    }
-
-    /**
-     * Create a subfolder for the specific assignment.
-     * This is called when the teacher creates an assignment and uploads the JUnit test files
-     *
-     * @param unitTestFileRef The text file which contains the JUnit tests meta data
-     * @param assignmentId    ID of the created assignment. Generated by Moodle
-     */
-    @RequestMapping(method = RequestMethod.POST)
-    public void uploadUnitTestFile(
-        @RequestParam("unitTestFile") MultipartFile unitTestFileRef,
-        @RequestParam("assignmentId") String assignmentId
-    ) throws IOException {
-        LOG.info("received new assignment");
-
-        File file = Paths.get(this.assignmentBasePath, assignmentId + ".txt").toFile();
-        file.mkdirs();
-
-        // save assignment config
-        unitTestFileRef.transferTo(file);
-        LOG.debug(String.format("saved config file to: %s", file.getAbsolutePath()));
-
-        String subDir="";
-        Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
-
-        Matcher config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
-        if (config == null) {
-        	pattern=Pattern.compile(RegexUtil.TESTCONFIGREGEX);
-        	config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
-        	if(config==null)
-        	{
-        		throw new RuntimeException("couldn't find repo config for unittest clone");
-        	}
-        }
-        else {
-        	subDir=config.group(4);
-        }
-        LOG.debug("calling test repo clone");
-        // cloning assignment repo to persistent space
-        repoUtil.cloneRepository(config, Paths.get(this.assignmentBasePath, assignmentId).toAbsolutePath().toString(), subDir);
-
-        LOG.info(String.format("stored new assignment: %s", file.getAbsolutePath()));
-    }
-
-    /**
-     * Delete the folder for the assignment.
-     * Called when the teacher deletes the JUnitTest assignment
-     * <p>
-     * {{url}}:8080/v1/unittest?assignmentId=111
-     *
-     * @param assignmentId ID of the assignment to delete. Generated by Moodle
-     */
-    @RequestMapping(method = RequestMethod.DELETE)
-    public void deleteUnitTestFiles(@RequestParam("assignmentId") String assignmentId) {
-        LOG.info(String.format("received deletion order for assignment %s", assignmentId));
-
-        // deleting config file
-        File file = Paths.get(
-            this.assignmentBasePath,
-            assignmentId + ".txt")
-            .toFile();
-        file.delete();
-
-        // deleting local copy of repository
-        file = Paths.get(
-            this.assignmentBasePath,
-            assignmentId).toFile();
-        FileSystemUtils.deleteRecursively(file);
-
-        LOG.info(String.format("assignment %s deletion complete", assignmentId));
-    }
-}
+package de.hftstuttgart.dtabackend.rest.v1.unittest;
+
+import de.hftstuttgart.dtabackend.utils.RepoUtil;
+import de.hftstuttgart.dtabackend.utils.RegexUtil;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.core.env.Environment;
+import org.springframework.util.FileSystemUtils;
+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 jakarta.servlet.annotation.MultipartConfig;
+
+import java.io.*;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Rest controller for anything related to the TEST files.
+ */
+@RestController
+@RequestMapping("/v1/unittest")
+@MultipartConfig
+public class UnitTestUpload {
+
+    private static final Logger LOG = LogManager.getLogger(UnitTestUpload.class);
+    private final RepoUtil repoUtil;
+    private final String assignmentBasePath;
+
+    public UnitTestUpload(Environment env, RepoUtil repoUtil) {
+        this.repoUtil = repoUtil;
+
+        Path p = Paths.get(env.getProperty("data.dir"), env.getProperty("data.dir.test.folder.name"));
+        this.assignmentBasePath = p.toAbsolutePath().toString();
+        LOG.debug(String.format("Assignment base path initialized as: %s", this.assignmentBasePath));
+    }
+
+    /**
+     * Create a subfolder for the specific assignment.
+     * This is called when the teacher creates an assignment and uploads the JUnit test files
+     *
+     * @param unitTestFileRef The text file which contains the JUnit tests meta data
+     * @param assignmentId    ID of the created assignment. Generated by Moodle
+     */
+    @RequestMapping(method = RequestMethod.POST)
+    public void uploadUnitTestFile(
+        @RequestParam("unitTestFile") MultipartFile unitTestFileRef,
+        @RequestParam("assignmentId") String assignmentId
+    ) throws IOException {
+        LOG.info("received new assignment");
+
+        File file = Paths.get(this.assignmentBasePath, assignmentId + ".txt").toFile();
+        file.mkdirs();
+
+        // save assignment config
+        unitTestFileRef.transferTo(file);
+        LOG.debug(String.format("saved config file to: %s", file.getAbsolutePath()));
+
+        String subDir="";
+        Pattern pattern = Pattern.compile(RegexUtil.DTA_TESTCONFIGREGEX);
+
+        Matcher config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
+        if (config == null) {
+        	pattern=Pattern.compile(RegexUtil.TESTCONFIGREGEX);
+        	config = RegexUtil.extractConfig(new FileInputStream(file), pattern);
+        	if(config==null)
+        	{
+        		throw new RuntimeException("couldn't find repo config for unittest clone");
+        	}
+        }
+        else {
+        	subDir=config.group(4);
+        }
+        LOG.debug("calling test repo clone");
+        // cloning assignment repo to persistent space
+        repoUtil.cloneRepository(config, Paths.get(this.assignmentBasePath, assignmentId).toAbsolutePath().toString(), subDir);
+
+        LOG.info(String.format("stored new assignment: %s", file.getAbsolutePath()));
+    }
+
+    /**
+     * Delete the folder for the assignment.
+     * Called when the teacher deletes the JUnitTest assignment
+     * <p>
+     * {{url}}:8080/v1/unittest?assignmentId=111
+     *
+     * @param assignmentId ID of the assignment to delete. Generated by Moodle
+     */
+    @RequestMapping(method = RequestMethod.DELETE)
+    public void deleteUnitTestFiles(@RequestParam("assignmentId") String assignmentId) {
+        LOG.info(String.format("received deletion order for assignment %s", assignmentId));
+
+        // deleting config file
+        File file = Paths.get(
+            this.assignmentBasePath,
+            assignmentId + ".txt")
+            .toFile();
+        file.delete();
+        LOG.debug(String.format("Deleted file: %s", file.getAbsolutePath()));
+
+        // deleting local copy of repository
+        file = Paths.get(
+            this.assignmentBasePath,
+            assignmentId).toFile();
+        FileSystemUtils.deleteRecursively(file);
+        LOG.debug(String.format("Deleted directory: %s", file.getAbsolutePath()));
+
+        LOG.info(String.format("assignment %s deletion complete", assignmentId));
+    }
+}
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java
index 0408c09..8bd3208 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ArchiveUtil.java
@@ -19,6 +19,7 @@ public class ArchiveUtil
 
             ZipEntry entry;
             while ((entry = stream.getNextEntry()) != null) {
+            LOG.debug(String.format("Processing zip entry: %s", entry.getName()));
                 File file = outDir.resolve(entry.getName()).toFile();
                 LOG.debug(String.format("processing entry %s", file.getAbsolutePath()));
 
@@ -28,6 +29,7 @@ public class ArchiveUtil
 
                     LOG.debug("creating new file");
                     file.createNewFile();
+                    LOG.debug(String.format("Created new file: %s", file.getAbsolutePath()));
 
                     byte[] buffer = new byte[2048];
                     try (FileOutputStream fos = new FileOutputStream(file);
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
index 3f1b8dc..1fb3861 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/ExecuteTestUtil.java
@@ -67,6 +67,16 @@ public class ExecuteTestUtil  {
             Paths.get(assignmentBasePath, assignmentId),
             testPath);
 
+		LOG.debug("copying exercise manifest from: %s in testPath: %s",
+		assignmentBasePath + assignmentId + "_checkout",
+		testPath.toString() );
+		Files.copy(Paths.get(
+			assignmentBasePath, assignmentId + "_checkout",
+			CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME),
+		 Paths.get(
+			testPath.toString(), 
+			CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME));
+
         LOG.debug("copy test config");
         Files.copy(
             Paths.get(assignmentBasePath, assignmentId + ".txt"),
@@ -140,14 +150,25 @@ public class ExecuteTestUtil  {
         
         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.debug(String.format(
+			"Reading Test Competency Profiles: basePath=%s, fileName=%s",
+			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, true));
         	
         	LOG.info("Checking for optional exercise competency profile information for paedagogical agent exercise recommendation functionality...");
         	//testPathHost or assignmentBasePath
-        	Path exerciseManifestFile = Paths.get(assignmentBasePath, CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+        	Path exerciseManifestFile = Paths.get(testPathHost.toString(), CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME);
+			LOG.debug(String.format(
+				"Constructing Path for exercise manifest: testPath=%s, fileName=%s -> Resulting Path=%s",
+				testPathHost.toString(),
+				CompetencyAssessmentUtil.EXERCISE_COMPETENCY_MANIFEST_FILE_NAME,
+				exerciseManifestFile.toString()
+			));
 			if (Files.exists(exerciseManifestFile)) {
 				LOG.info("Found optional exercise competency profiles, generating recommendations...");
 				resultSummary.recommendations = recommendNextExercises(assignmentId, testPathHost, testCompetencyProfiles, resultSummary);
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java
index edf8324..f185b9c 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/FileUtil.java
@@ -24,10 +24,12 @@ public class FileUtil {
                     deleteFolderRecursively(f);
                 } else {
                     f.delete();
+                System.out.println(String.format("Deleted file: %s", f.getAbsolutePath()));
                 }
             }
         }
         folder.delete();
+        System.out.println(String.format("Deleted folder: %s", folder.getAbsolutePath()));
     }
 
     public static void copyFolder(Path src, Path dst) throws IOException {
@@ -35,6 +37,7 @@ public class FileUtil {
             .forEach(source -> {
                 try {
                     Files.copy(source, dst.resolve(src.relativize(source)));
+                    System.out.println(String.format("Copying file from: %s to %s", source.toString(), dst.resolve(src.relativize(source)).toString()));
                 } catch (IOException e) {
                     throw new RuntimeException(e.getMessage(), e);
                 }
diff --git a/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java b/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java
index f2c8587..334bcd7 100644
--- a/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java
+++ b/src/main/java/de/hftstuttgart/dtabackend/utils/RepoUtil.java
@@ -104,7 +104,7 @@ public class RepoUtil {
             	//copy appropriate path from checkout directory to target directory
             	FileSystemUtils.copyRecursively(new File(checkoutDirectory+subDir), new File(targetPath));
             }
-            LOG.debug(String.format("cloned from %s to %s", config.group(1), targetDirectory));            
+            LOG.debug(String.format("cloned from %s via %s to %s", config.group(1), checkoutDirectory+subDir, targetDirectory));            
         }
         catch (IOException e) {
         	LOG.error(String.format("Error while cloning from %s: could not copy to unit test dir", config.group(1)), e);
-- 
GitLab