diff --git a/.github/workflows/complete-e2e.yml b/.github/workflows/complete-e2e.yml index 332cc834e1..2c3f9741b7 100644 --- a/.github/workflows/complete-e2e.yml +++ b/.github/workflows/complete-e2e.yml @@ -70,6 +70,8 @@ jobs: {zip: "folderMultiRoot.zip", name: "folderMultiRoot", folder: "f0", language: "java", cliArgs: "--new f1"}, {zip: "mixedMultiRoot.zip", name: "mixedBaseFile", folder: "f0", language: "java", cliArgs: "--new f1"}, {zip: "mixedMultiRoot.zip", name: "mixedBaseFolder", folder: "f1", language: "java", cliArgs: "--new f0"}, + {zip: "singleNewSubmission.zip", name: "singleNewSubmission", folder: "2023", language: "java", cliArgs: "-old 2022,2021,2020"}, + {zip: "submissionsWithSameName.zip", name: "submissionsWithSameName", folder: "2023", language: "java", cliArgs: "-old old/2022,old/2021,old/2020"}, {zip: "cpp.zip", name: "cpp", folder: "./cpp", language: "cpp", cliArgs: ""}, {zip: "csharp.zip", name: "csharp", folder: "./csharp", language: "csharp", cliArgs: ""}, {zip: "python.zip", name: "python", folder: "./python", language: "python3", cliArgs: ""} diff --git a/.github/workflows/files/singleNewSubmission.zip b/.github/workflows/files/singleNewSubmission.zip new file mode 100644 index 0000000000..e736486091 Binary files /dev/null and b/.github/workflows/files/singleNewSubmission.zip differ diff --git a/.github/workflows/files/submissionsWithSameName.zip b/.github/workflows/files/submissionsWithSameName.zip new file mode 100644 index 0000000000..56274385ab Binary files /dev/null and b/.github/workflows/files/submissionsWithSameName.zip differ diff --git a/core/src/main/java/de/jplag/JPlag.java b/core/src/main/java/de/jplag/JPlag.java index 41f1c08c8e..9652382d33 100644 --- a/core/src/main/java/de/jplag/JPlag.java +++ b/core/src/main/java/de/jplag/JPlag.java @@ -1,18 +1,14 @@ package de.jplag; import java.io.File; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.ResourceBundle; -import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.jplag.clustering.ClusteringFactory; import de.jplag.exceptions.ExitException; -import de.jplag.exceptions.RootDirectoryException; import de.jplag.exceptions.SubmissionException; import de.jplag.merging.MatchMerging; import de.jplag.options.JPlagOptions; @@ -107,30 +103,9 @@ private static void logSkippedSubmissions(SubmissionSet submissionSet, JPlagOpti } } - private static void checkForConfigurationConsistency(JPlagOptions options) throws RootDirectoryException { + private static void checkForConfigurationConsistency(JPlagOptions options) { if (options.normalize() && !options.language().supportsNormalization()) { - logger.error(String.format("The language %s cannot be used with normalization.", options.language().getName())); + logger.error("The language {} cannot be used with normalization.", options.language().getName()); } - - List duplicateNames = getDuplicateSubmissionFolderNames(options); - if (duplicateNames.size() > 0) { - throw new RootDirectoryException(String.format("Duplicate root directory names found: %s", String.join(", ", duplicateNames))); - } - } - - private static List getDuplicateSubmissionFolderNames(JPlagOptions options) { - List duplicateNames = new ArrayList<>(); - Set alreadyFoundNames = new HashSet<>(); - for (File file : options.submissionDirectories()) { - if (!alreadyFoundNames.add(file.getName())) { - duplicateNames.add(file.getName()); - } - } - for (File file : options.oldSubmissionDirectories()) { - if (!alreadyFoundNames.add(file.getName())) { - duplicateNames.add(file.getName()); - } - } - return duplicateNames; } } diff --git a/core/src/main/java/de/jplag/SubmissionSetBuilder.java b/core/src/main/java/de/jplag/SubmissionSetBuilder.java index c74252fba8..56cab68859 100644 --- a/core/src/main/java/de/jplag/SubmissionSetBuilder.java +++ b/core/src/main/java/de/jplag/SubmissionSetBuilder.java @@ -14,6 +14,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,10 +82,13 @@ public SubmissionSet buildSubmissionSet() throws ExitException { submissionFiles.addAll(listSubmissionFiles(submissionDirectory, false)); } + Set allRootDirectories = submissionFiles.stream().map(SubmissionFileData::root).collect(Collectors.toSet()); + Map rootDirectoryNamePrefixesMapper = getRootDirectoryNamesPrefixesMapper(allRootDirectories); + ProgressBar progressBar = ProgressBarLogger.createProgressBar(ProgressBarType.LOADING, submissionFiles.size()); Map foundSubmissions = new HashMap<>(); for (SubmissionFileData submissionFile : submissionFiles) { - processSubmissionFile(submissionFile, multipleRoots, foundSubmissions); + processSubmissionFile(submissionFile, multipleRoots, rootDirectoryNamePrefixesMapper, foundSubmissions); progressBar.step(); } progressBar.dispose(); @@ -103,6 +108,92 @@ public SubmissionSet buildSubmissionSet() throws ExitException { return new SubmissionSet(submissions, baseCodeSubmission.orElse(null), options); } + private static String[] getCanonicalPathComponents(File path) { + try { + return path.getCanonicalPath().split(File.separator.equals("\\") ? "\\\\" : File.separator); + } catch (Exception e) { + throw new RuntimeException("Error getting canonical path", e); + } + } + + public static File getCommonAncestor(File firstPath, File secondPath) { + String[] firstComponents = getCanonicalPathComponents(firstPath); + String[] secondComponents = getCanonicalPathComponents(secondPath); + + int minLength = Math.min(firstComponents.length, secondComponents.length); + int commonLength = 0; + + for (int i = 0; i < minLength; i++) { + if (firstComponents[i].equals(secondComponents[i])) { + commonLength++; + } else { + break; + } + } + + if (commonLength == 0) { + return null; + } + + StringBuilder commonPath = new StringBuilder(firstComponents[0]); + for (int i = 1; i < commonLength; i++) { + commonPath.append(File.separator).append(firstComponents[i]); + } + + return new File(commonPath.toString()); + } + + private String findCommonPathPrefix(List canonicalPaths) { + if (canonicalPaths == null) { + return ""; + } + + File prefix = canonicalPaths.getFirst(); + for (int i = 1; i < canonicalPaths.size(); i++) { + prefix = getCommonAncestor(prefix, canonicalPaths.get(i)); + } + + return prefix == null ? null : prefix.toString(); + } + + private String getPathPrefix(File path, String commonPrefix) { + String result = path.toString().substring(commonPrefix.length()); + return result.startsWith(File.separator) ? result.substring(1) : result; + } + + private Map getRootDirectoryNamesPrefixesMapper(Set allRootDirectories) { + Map> conflicts = getRootDirectoryNameConflicts(allRootDirectories); + + Map result = new HashMap<>(); + conflicts.forEach((name, paths) -> { + if (paths.size() > 1) { + String commonPrefix = findCommonPathPrefix(paths); + for (File path : paths) { + result.put(path, getPathPrefix(path, commonPrefix)); + } + } else { + result.put(paths.getFirst(), ""); + } + }); + + return result; + } + + private static Map> getRootDirectoryNameConflicts(Set allRootDirectories) { + Map> conflicts = new HashMap<>(); + + for (File rootDir : allRootDirectories) { + String roodDirName = rootDir.getName(); + if (conflicts.containsKey(roodDirName)) { + conflicts.get(roodDirName).add(rootDir); + } else { + conflicts.put(roodDirName, Stream.of(rootDir).collect(Collectors.toList())); + } + } + + return conflicts; + } + /** * Verify that the given root directories exist and have no duplicate entries. */ @@ -219,14 +310,17 @@ private Submission processSubmission(String submissionName, File submissionFile, return new Submission(submissionName, file, isNew, parseFilesRecursively(file), options.language()); } - private void processSubmissionFile(SubmissionFileData file, boolean multipleRoots, Map foundSubmissions) throws ExitException { + private void processSubmissionFile(SubmissionFileData file, boolean multipleRoots, Map rootDirectoryNamePrefixesMapper, + Map foundSubmissions) throws ExitException { if (isFileExcluded(file.submissionFile())) { logger.error("Exclude submission: {}", file.submissionFile().getName()); } else if (file.submissionFile().isFile() && !hasValidSuffix(file.submissionFile())) { logger.error("Ignore submission with invalid suffix: {}", file.submissionFile().getName()); } else { - String rootDirectoryPrefix = multipleRoots ? (file.root().getName() + File.separator) : ""; - String submissionName = rootDirectoryPrefix + file.submissionFile().getName(); + String rootDirectoryPrefix = rootDirectoryNamePrefixesMapper.get(file.root()); + rootDirectoryPrefix = rootDirectoryPrefix.isEmpty() && multipleRoots ? file.root().getName() : rootDirectoryPrefix; + String submissionName = rootDirectoryPrefix.isEmpty() ? file.submissionFile().getName() + : rootDirectoryPrefix + File.separator + file.submissionFile().getName(); Submission submission = processSubmission(submissionName, file.submissionFile(), file.isNew()); foundSubmissions.put(submission.getRoot(), submission); } diff --git a/core/src/test/java/de/jplag/RootFolderTest.java b/core/src/test/java/de/jplag/RootFolderTest.java index c5acd3cb1f..945d2f5b87 100644 --- a/core/src/test/java/de/jplag/RootFolderTest.java +++ b/core/src/test/java/de/jplag/RootFolderTest.java @@ -1,16 +1,16 @@ package de.jplag; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; import java.io.File; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import de.jplag.exceptions.ExitException; -import de.jplag.exceptions.RootDirectoryException; /** * Test class for the multi-root feature and the old-new feature. @@ -71,7 +71,7 @@ void testDisjunctNewAndOldRootDirectories() throws ExitException { void testOverlappingNewAndOldDirectoriesOverlap() throws ExitException { List newDirectories = List.of(getBasePath(ROOT_2)); List oldDirectories = List.of(getBasePath(ROOT_2)); - assertThrows(RootDirectoryException.class, () -> runJPlag(newDirectories, oldDirectories, it -> it)); + assertDoesNotThrow(() -> runJPlag(newDirectories, oldDirectories, it -> it)); } @Test @@ -84,4 +84,41 @@ void testBasecodeInOldDirectory() throws ExitException { int numberOfExpectedComparison = 1 + ROOT_COUNT_2 * (ROOT_COUNT_1 - 1); // -1 for basecode assertEquals(numberOfExpectedComparison, result.getAllComparisons().size()); } + + @Test + @DisplayName("test multiple submissions with same folder name") + void testSubmissionsWithSameFolderName() throws ExitException { + List newSubmissionsNames = List.of("2023"); + List oldSubmissionsNames = List.of("2022", "2021", "2020"); + List newSubmissions = newSubmissionsNames.stream().map(it -> getBasePath("SubmissionsWithSameName" + File.separator + it)).toList(); + List oldSubmissions = oldSubmissionsNames.stream() + .map(it -> getBasePath("SubmissionsWithSameName" + File.separator + "old" + File.separator + it)).toList(); + + JPlagResult result = runJPlag(newSubmissions, oldSubmissions, it -> it); + + long numberOfNewSubmissions = result.getSubmissions().getSubmissions().stream().filter(Submission::isNew).count(); + long numberOfOldSubmissions = result.getSubmissions().getSubmissions().stream().filter(it -> !it.isNew()).count(); + assertEquals(2, numberOfNewSubmissions); + assertEquals(6, numberOfOldSubmissions); + + Set submissionNames = result.getSubmissions().getSubmissions().stream().map(Submission::getName).collect(Collectors.toSet()); + Set expectedNames = Set.of("2023/gr1", "2023/gr2", "2022/gr1", "2022/gr2", "2021/gr1", "2021/gr2", "2020/gr1", "2020/gr2"); + assertEquals(expectedNames, submissionNames); + } + + @Test + @DisplayName("test single new submission") + void testSingleNewSubmission() throws ExitException { + List newSubmissionsNames = List.of("2023"); + List oldSubmissionsNames = List.of("2022", "2021", "2020"); + List newSubmissions = newSubmissionsNames.stream().map(it -> getBasePath("SingleNewSubmission" + File.separator + it)).toList(); + List oldSubmissions = oldSubmissionsNames.stream().map(it -> getBasePath("SingleNewSubmission" + File.separator + it)).toList(); + + JPlagResult result = runJPlag(newSubmissions, oldSubmissions, it -> it); + + long numberOfNewSubmissions = result.getSubmissions().getSubmissions().stream().filter(Submission::isNew).count(); + long numberOfOldSubmissions = result.getSubmissions().getSubmissions().stream().filter(it -> !it.isNew()).count(); + assertEquals(1, numberOfNewSubmissions); + assertEquals(3, numberOfOldSubmissions); + } } diff --git a/core/src/test/java/de/jplag/reporting/reportobject/ReportObjectFactoryTest.java b/core/src/test/java/de/jplag/reporting/reportobject/ReportObjectFactoryTest.java index 36a7f610dd..64ff032a6f 100644 --- a/core/src/test/java/de/jplag/reporting/reportobject/ReportObjectFactoryTest.java +++ b/core/src/test/java/de/jplag/reporting/reportobject/ReportObjectFactoryTest.java @@ -6,6 +6,8 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -37,6 +39,27 @@ void testCreateAndSaveReportWithBasecode() throws ExitException, IOException { assertTrue(isArchive(testZip)); } + @Test + void testWithSamenameSubmissions() throws ExitException, IOException { + File submission1 = new File(BASE_PATH, "basecode/A"); + File submission2 = new File(BASE_PATH, "basecode/B"); + File submission3 = new File(BASE_PATH, "basecode-sameNameOfSubdirectoryAndRootdirectory/A"); + File submission4 = new File(BASE_PATH, "basecode-sameNameOfSubdirectoryAndRootdirectory/A"); + List submissions = Stream.of(submission1, submission2, submission3, submission4).map(File::toString).toList(); + JPlagResult result = runJPlag(submissions, it -> it.withBaseCodeSubmissionDirectory(new File(BASE_PATH, BASECODE_BASE))); + File testZip = File.createTempFile("result", ".zip"); + ReportObjectFactory reportObjectFactory = new ReportObjectFactory(testZip); + reportObjectFactory.createAndSaveReport(result); + + assertNotNull(result); + assertTrue(isArchive(testZip)); + + String[] expectedSubmissionNames = new String[] {"B/TerrainType.java", "basecode/A/TerrainType.java"}; + for (String expected : expectedSubmissionNames) { + assertTrue(result.getSubmissions().getSubmissions().stream().anyMatch(submission -> submission.getName().equals(expected))); + } + } + /** * Checks if the given file is a valid archive * @param file The file to check diff --git a/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2020/QSort2020.java b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2020/QSort2020.java new file mode 100644 index 0000000000..1fdfb3562f --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2020/QSort2020.java @@ -0,0 +1,40 @@ +import java.util.ArrayList; +import java.util.List; + +public class QSort2020 { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + List sortedList = quickSort(List.of(array)); + return sortedList.toArray(new String[0]); + } + + private List quickSort(List list) { + if (list.size() <= 1) { + return list; + } + + String pivot = list.get(list.size() / 2); + List less = new ArrayList<>(); + List equal = new ArrayList<>(); + List greater = new ArrayList<>(); + + for (String s : list) { + int comparison = s.compareTo(pivot); + if (comparison < 0) { + less.add(s); + } else if (comparison > 0) { + greater.add(s); + } else { + equal.add(s); + } + } + + List sorted = new ArrayList<>(); + sorted.addAll(quickSort(less)); + sorted.addAll(equal); + sorted.addAll(quickSort(greater)); + return sorted; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2021/QSort2021.java b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2021/QSort2021.java new file mode 100644 index 0000000000..7fc96abd1b --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2021/QSort2021.java @@ -0,0 +1,44 @@ +import java.util.Stack; + +public class QSort2021 { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + + Stack stack = new Stack<>(); + stack.push(new int[] { 0, array.length - 1 }); + + while (!stack.isEmpty()) { + int[] range = stack.pop(); + int low = range[0], high = range[1]; + + if (low < high) { + int pivotIndex = partition(array, low, high); + stack.push(new int[] { low, pivotIndex - 1 }); + stack.push(new int[] { pivotIndex + 1, high }); + } + } + + return array; + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2022/QSort2022.java b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2022/QSort2022.java new file mode 100644 index 0000000000..7b7dd9102b --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2022/QSort2022.java @@ -0,0 +1,41 @@ +public class QSort2022 { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + quickSort(array, 0, array.length - 1); + return array; + } + + private void quickSort(String[] array, int low, int high) { + while (low < high) { + int pivotIndex = partition(array, low, high); + if (pivotIndex - low < high - pivotIndex) { + quickSort(array, low, pivotIndex - 1); + low = pivotIndex + 1; + } else { + quickSort(array, pivotIndex + 1, high); + high = pivotIndex - 1; + } + } + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2023/QSort2023.java b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2023/QSort2023.java new file mode 100644 index 0000000000..dfe08fddbb --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SingleNewSubmission/2023/QSort2023.java @@ -0,0 +1,37 @@ +public class QSort2023 { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + quickSort(array, 0, array.length - 1); + return array; + } + + private void quickSort(String[] array, int low, int high) { + if (low < high) { + int pivotIndex = partition(array, low, high); + quickSort(array, low, pivotIndex - 1); + quickSort(array, pivotIndex + 1, high); + } + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/2023/gr1/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/2023/gr1/QSort.java new file mode 100644 index 0000000000..091f8a67af --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/2023/gr1/QSort.java @@ -0,0 +1,37 @@ +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + quickSort(array, 0, array.length - 1); + return array; + } + + private void quickSort(String[] array, int low, int high) { + if (low < high) { + int pivotIndex = partition(array, low, high); + quickSort(array, low, pivotIndex - 1); + quickSort(array, pivotIndex + 1, high); + } + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/2023/gr2/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/2023/gr2/QSort.java new file mode 100644 index 0000000000..091f8a67af --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/2023/gr2/QSort.java @@ -0,0 +1,37 @@ +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + quickSort(array, 0, array.length - 1); + return array; + } + + private void quickSort(String[] array, int low, int high) { + if (low < high) { + int pivotIndex = partition(array, low, high); + quickSort(array, low, pivotIndex - 1); + quickSort(array, pivotIndex + 1, high); + } + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } + +} \ No newline at end of file diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2020/gr1/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2020/gr1/QSort.java new file mode 100644 index 0000000000..e8e36f67da --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2020/gr1/QSort.java @@ -0,0 +1,40 @@ +import java.util.ArrayList; +import java.util.List; + +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + List sortedList = quickSort(List.of(array)); + return sortedList.toArray(new String[0]); + } + + private List quickSort(List list) { + if (list.size() <= 1) { + return list; + } + + String pivot = list.get(list.size() / 2); + List less = new ArrayList<>(); + List equal = new ArrayList<>(); + List greater = new ArrayList<>(); + + for (String s : list) { + int comparison = s.compareTo(pivot); + if (comparison < 0) { + less.add(s); + } else if (comparison > 0) { + greater.add(s); + } else { + equal.add(s); + } + } + + List sorted = new ArrayList<>(); + sorted.addAll(quickSort(less)); + sorted.addAll(equal); + sorted.addAll(quickSort(greater)); + return sorted; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2020/gr2/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2020/gr2/QSort.java new file mode 100644 index 0000000000..e8e36f67da --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2020/gr2/QSort.java @@ -0,0 +1,40 @@ +import java.util.ArrayList; +import java.util.List; + +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + List sortedList = quickSort(List.of(array)); + return sortedList.toArray(new String[0]); + } + + private List quickSort(List list) { + if (list.size() <= 1) { + return list; + } + + String pivot = list.get(list.size() / 2); + List less = new ArrayList<>(); + List equal = new ArrayList<>(); + List greater = new ArrayList<>(); + + for (String s : list) { + int comparison = s.compareTo(pivot); + if (comparison < 0) { + less.add(s); + } else if (comparison > 0) { + greater.add(s); + } else { + equal.add(s); + } + } + + List sorted = new ArrayList<>(); + sorted.addAll(quickSort(less)); + sorted.addAll(equal); + sorted.addAll(quickSort(greater)); + return sorted; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2021/gr1/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2021/gr1/QSort.java new file mode 100644 index 0000000000..d548b6fb04 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2021/gr1/QSort.java @@ -0,0 +1,44 @@ +import java.util.Stack; + +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + + Stack stack = new Stack<>(); + stack.push(new int[] { 0, array.length - 1 }); + + while (!stack.isEmpty()) { + int[] range = stack.pop(); + int low = range[0], high = range[1]; + + if (low < high) { + int pivotIndex = partition(array, low, high); + stack.push(new int[] { low, pivotIndex - 1 }); + stack.push(new int[] { pivotIndex + 1, high }); + } + } + + return array; + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2021/gr2/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2021/gr2/QSort.java new file mode 100644 index 0000000000..d548b6fb04 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2021/gr2/QSort.java @@ -0,0 +1,44 @@ +import java.util.Stack; + +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + + Stack stack = new Stack<>(); + stack.push(new int[] { 0, array.length - 1 }); + + while (!stack.isEmpty()) { + int[] range = stack.pop(); + int low = range[0], high = range[1]; + + if (low < high) { + int pivotIndex = partition(array, low, high); + stack.push(new int[] { low, pivotIndex - 1 }); + stack.push(new int[] { pivotIndex + 1, high }); + } + } + + return array; + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2022/gr1/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2022/gr1/QSort.java new file mode 100644 index 0000000000..4730b95303 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2022/gr1/QSort.java @@ -0,0 +1,41 @@ +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + quickSort(array, 0, array.length - 1); + return array; + } + + private void quickSort(String[] array, int low, int high) { + while (low < high) { + int pivotIndex = partition(array, low, high); + if (pivotIndex - low < high - pivotIndex) { + quickSort(array, low, pivotIndex - 1); + low = pivotIndex + 1; + } else { + quickSort(array, pivotIndex + 1, high); + high = pivotIndex - 1; + } + } + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2022/gr2/QSort.java b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2022/gr2/QSort.java new file mode 100644 index 0000000000..4730b95303 --- /dev/null +++ b/core/src/test/resources/de/jplag/samples/SubmissionsWithSameName/old/2022/gr2/QSort.java @@ -0,0 +1,41 @@ +public class QSort { + public String[] qsort(String[] array) { + if (array == null || array.length == 0) { + return array; + } + quickSort(array, 0, array.length - 1); + return array; + } + + private void quickSort(String[] array, int low, int high) { + while (low < high) { + int pivotIndex = partition(array, low, high); + if (pivotIndex - low < high - pivotIndex) { + quickSort(array, low, pivotIndex - 1); + low = pivotIndex + 1; + } else { + quickSort(array, pivotIndex + 1, high); + high = pivotIndex - 1; + } + } + } + + private int partition(String[] array, int low, int high) { + String pivot = array[high]; + int i = low - 1; + for (int j = low; j < high; j++) { + if (array[j].compareTo(pivot) <= 0) { + i++; + swap(array, i, j); + } + } + swap(array, i + 1, high); + return i + 1; + } + + private void swap(String[] array, int i, int j) { + String temp = array[i]; + array[i] = array[j]; + array[j] = temp; + } +} diff --git a/report-viewer/tests/e2e/OpenComparisonTest.spec.ts b/report-viewer/tests/e2e/OpenComparisonTest.spec.ts index 8b9a9bdd16..6577953f1e 100644 --- a/report-viewer/tests/e2e/OpenComparisonTest.spec.ts +++ b/report-viewer/tests/e2e/OpenComparisonTest.spec.ts @@ -8,6 +8,8 @@ interface DataSet { secondSubmissionName: string } +const regexPathSeparator = '(\\\\|\\/)'; + const testSets: DataSet[] = [ { datasetName: 'fileSingleRoot-report.zip', @@ -39,6 +41,16 @@ const testSets: DataSet[] = [ firstSubmissionName: 'f0\\\\|/0', secondSubmissionName: 'f1\\\\|/1' }, + { + datasetName: 'singleNewSubmission-report.zip', + firstSubmissionName: `2023${regexPathSeparator}QSort2023.java`, + secondSubmissionName: `2022${regexPathSeparator}QSort2022.java`, + }, + { + datasetName: 'submissionsWithSameName-report.zip', + firstSubmissionName: `2023${regexPathSeparator}gr1`, + secondSubmissionName: `2023${regexPathSeparator}gr2` + }, { datasetName: 'python-report.zip', firstSubmissionName: '01.py',