diff --git a/README.md b/README.md index c84ecd706..9cfe85928 100644 --- a/README.md +++ b/README.md @@ -39,23 +39,27 @@ JPlag can either be used via the CLI or directly via its Java API. For more info ``` JPlag - Detecting Software Plagiarism -Usage: JPlag [ options ] [ ...] - Root-directory that contains submissions +Usage: JPlag [ options ] [ ... ] [ -new ... ] [ -old ... ] + Root-directory with submissions to check for plagiarism + Root-directory with submissions to check for plagiarism + Root-directory with prior submissions to compare against named arguments: - -h, --help show this help message and exit - -l {java,python3,cpp,csharp,char,text,scheme} Select the language to parse the submissions (default: java) - -bc BC Path of the directory containing the base code (common framework used in all submissions) - -v {quiet,long} Verbosity of the logging (default: quiet) - -d Debug parser. Non-parsable files will be stored (default false) - -S S Look in directories /*/ for programs - -p P comma-separated list of all filename suffixes that are included - -x X All files named in this file will be ignored in the comparison (line-separated list) - -t T Tunes the comparison sensitivity by adjusting the minimum token required to be counted as a matching section. A smaller increases the sensitivity but might lead to more false-positives - -m M Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved (default: 0.0) - -n N The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown (default: 30) - -r R Name of the directory in which the comparison results will be stored (default: result) - -c {normal,parallel} Comparison mode used to compare the programs (default: normal) + -h, --help show this help message and exit + -l {java,python3,cpp,csharp,char,text,scheme} Select the language to parse the submissions (default: java) + -bc BC Path of the directory containing the base code (common framework used in all submissions) + -v {quiet,long} Verbosity of the logging (default: quiet) + -d Debug parser. Non-parsable files will be stored (default: false) + -S S Look in directories /*/ for programs + -p P comma-separated list of all filename suffixes that are included + -x X All files named in this file will be ignored in the comparison (line-separated list) + -t T Tunes the comparison sensitivity by adjusting the minimum token required to be counted as a matching section. A smaller + increases the sensitivity but might lead to more false-positives + -m M Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved (default: 0.0) + -n N The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown + (default: 30) + -r R Name of the directory in which the comparison results will be stored (default: result) + -c {normal,parallel} Comparison mode used to compare the programs (default: normal) ``` ### Java API @@ -63,7 +67,7 @@ named arguments: The new API makes it easy to integrate JPlag's plagiarism detection into external Java projects: ```java -JPlagOptions options = new JPlagOptions(List.of("/path/to/rootDir"), LanguageOption.JAVA); +JPlagOptions options = new JPlagOptions(List.of("/path/to/rootDir"), List.of(), LanguageOption.JAVA); options.setBaseCodeSubmissionName("template"); JPlag jplag = new JPlag(options); diff --git a/jplag/src/main/java/de/jplag/CLI.java b/jplag/src/main/java/de/jplag/CLI.java index 28ece98fe..5410e5ab0 100644 --- a/jplag/src/main/java/de/jplag/CLI.java +++ b/jplag/src/main/java/de/jplag/CLI.java @@ -2,6 +2,8 @@ import static de.jplag.CommandLineArgument.*; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import java.util.Random; @@ -98,8 +100,16 @@ public JPlagOptions buildOptionsFromArguments(Namespace namespace) { if (fileSuffixString != null) { fileSuffixes = fileSuffixString.replaceAll("\\s+", "").split(","); } + + // Collect the root directories. + List submissionDirectories = new ArrayList<>(); + List oldSubmissionDirectories = new ArrayList<>(); + addAllMultiValueArgument(ROOT_DIRECTORY.getListFrom(namespace), submissionDirectories); + addAllMultiValueArgument(NEW_DIRECTORY.getListFrom(namespace), submissionDirectories); + addAllMultiValueArgument(OLD_DIRECTORY.getListFrom(namespace), oldSubmissionDirectories); + LanguageOption language = LanguageOption.fromDisplayName(LANGUAGE.getFrom(namespace)); - JPlagOptions options = new JPlagOptions(ROOT_DIRECTORY.getListFrom(namespace), language); + JPlagOptions options = new JPlagOptions(submissionDirectories, oldSubmissionDirectories, language); options.setBaseCodeSubmissionName(BASE_CODE.getFrom(namespace)); options.setVerbosity(Verbosity.fromOption(VERBOSITY.getFrom(namespace))); options.setDebugParser(DEBUG.getFrom(namespace)); @@ -152,4 +162,12 @@ private String generateDescription() { var randomDescription = DESCRIPTIONS[new Random().nextInt(DESCRIPTIONS.length)]; return String.format("JPlag - %s" + System.lineSeparator() + CREDITS, randomDescription); } + + private void addAllMultiValueArgument(List> argumentValues, List destinationRootDirectories) { + if (argumentValues == null) { + return; + } + + argumentValues.stream().forEach(value -> destinationRootDirectories.addAll(value)); + } } diff --git a/jplag/src/main/java/de/jplag/CommandLineArgument.java b/jplag/src/main/java/de/jplag/CommandLineArgument.java index 4cadd39b8..bcffd581c 100644 --- a/jplag/src/main/java/de/jplag/CommandLineArgument.java +++ b/jplag/src/main/java/de/jplag/CommandLineArgument.java @@ -5,6 +5,7 @@ import static de.jplag.options.JPlagOptions.DEFAULT_COMPARISON_MODE; import static de.jplag.options.JPlagOptions.DEFAULT_SHOWN_COMPARISONS; import static de.jplag.options.JPlagOptions.DEFAULT_SIMILARITY_THRESHOLD; +import static net.sourceforge.argparse4j.impl.Arguments.append; import static net.sourceforge.argparse4j.impl.Arguments.storeTrue; import java.util.Collection; @@ -30,8 +31,9 @@ * @author Timur Saglam */ public enum CommandLineArgument { - - ROOT_DIRECTORY(new Builder("rootDir", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)), + ROOT_DIRECTORY(new Builder("rootDir", String.class).nargs(NumberOfArgumentValues.ZERO_OR_MORE_VALUES)), + NEW_DIRECTORY(new Builder("-new", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)), + OLD_DIRECTORY(new Builder("-old", String.class).nargs(NumberOfArgumentValues.ONE_OR_MORE_VALUES)), LANGUAGE(new Builder("-l", String.class).defaultsTo(LanguageOption.getDefault().getDisplayName()).choices(LanguageOption.getAllDisplayNames())), BASE_CODE("-bc", String.class), VERBOSITY(new Builder("-v", String.class).defaultsTo("quiet").choices(List.of("quiet", "long"))), // TODO SH: Replace verbosity when integrating a @@ -167,8 +169,12 @@ public void parseWith(ArgumentParser parser, CliGroupHelper groupHelper) { if (type == Boolean.class) { argument.action(storeTrue()); } - if (numberOfValues == NumberOfArgumentValues.ONE_OR_MORE_VALUES) { + if (!numberOfValues.toString().isEmpty()) { + // For multi-value arguments keep all invocations. + // This causes the argument value to change its type to 'List>'. + // Also, when the retrieved value after parsing the CLI is 'null', the argument is not used. argument.nargs(numberOfValues.toString()); + argument.action(append()); } } diff --git a/jplag/src/main/java/de/jplag/NumberOfArgumentValues.java b/jplag/src/main/java/de/jplag/NumberOfArgumentValues.java index 3b0574d1f..8a4987c2a 100644 --- a/jplag/src/main/java/de/jplag/NumberOfArgumentValues.java +++ b/jplag/src/main/java/de/jplag/NumberOfArgumentValues.java @@ -5,7 +5,8 @@ */ public enum NumberOfArgumentValues { SINGLE_VALUE(""), - ONE_OR_MORE_VALUES("+"),; + ONE_OR_MORE_VALUES("+"), + ZERO_OR_MORE_VALUES("*"); private final String representation; diff --git a/jplag/src/main/java/de/jplag/Submission.java b/jplag/src/main/java/de/jplag/Submission.java index 89bc458b3..6f3d7430b 100644 --- a/jplag/src/main/java/de/jplag/Submission.java +++ b/jplag/src/main/java/de/jplag/Submission.java @@ -35,6 +35,11 @@ public class Submission implements Comparable { */ private final File submissionRootFile; + /** + * Whether the submission is new. That is, must be checked for plagiarism. + */ + private final boolean isNew; + /** * Files of the submission. */ @@ -62,13 +67,15 @@ public class Submission implements Comparable { * Creates a submission. * @param name Identification of the submission (directory or filename). * @param submissionRootFile is the submission file, or the root of the submission itself. + * @param isNew states whether the submission must be checked for plagiarism. * @param files are the files of the submissions, if the root is a single file it should just contain one file. * @param language is the language of the submission. * @param errorCollector is the interface for error reporting. */ - public Submission(String name, File submissionRootFile, Collection files, Language language, ErrorCollector errorCollector) { + public Submission(String name, File submissionRootFile, boolean isNew, Collection files, Language language, ErrorCollector errorCollector) { this.name = name; this.submissionRootFile = submissionRootFile; + this.isNew = isNew; this.files = files; this.language = language; this.errorCollector = errorCollector; @@ -96,6 +103,13 @@ public File getRoot() { return submissionRootFile; } + /** + * @return whether the submission is new, That is, must be checked for plagiarism. + */ + public boolean isNew() { + return isNew; + } + /** * @return Number of tokens in the parse result. */ diff --git a/jplag/src/main/java/de/jplag/SubmissionSetBuilder.java b/jplag/src/main/java/de/jplag/SubmissionSetBuilder.java index ecf89eb9e..df68b636d 100644 --- a/jplag/src/main/java/de/jplag/SubmissionSetBuilder.java +++ b/jplag/src/main/java/de/jplag/SubmissionSetBuilder.java @@ -51,19 +51,25 @@ public SubmissionSetBuilder(Language language, JPlagOptions options, ErrorCollec * @throws ExitException if the directory cannot be read. */ public SubmissionSet buildSubmissionSet() throws ExitException { - Set rootDirectoryNames = verifyRootDirectories(options.getRootDirectoryNames()); + Set submissionDirectories = verifyRootDirectories(options.getSubmissionDirectories(), true); + Set oldSubmissionDirectories = verifyRootDirectories(options.getOldSubmissionDirectories(), false); + checkForNonOverlappingRootDirectories(submissionDirectories, oldSubmissionDirectories); // For backward compatibility, don't prefix submission names with their root directory // if there is only one root directory. - boolean multipleRoots = (rootDirectoryNames.size() > 1); + int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size(); + boolean multipleRoots = (numberOfRootDirectories > 1); // Collect valid looking entries from the root directories. Map foundSubmissions = new HashMap<>(); - for (File rootDirectory : rootDirectoryNames) { - processRootDirectoryEntries(rootDirectory, multipleRoots, foundSubmissions); + for (File directory : submissionDirectories) { + processRootDirectoryEntries(directory, multipleRoots, foundSubmissions, true); + } + for (File oldDirectory : oldSubmissionDirectories) { + processRootDirectoryEntries(oldDirectory, multipleRoots, foundSubmissions, false); } - Optional baseCodeSubmission = loadBaseCode(rootDirectoryNames, foundSubmissions); + Optional baseCodeSubmission = loadBaseCode(submissionDirectories, oldSubmissionDirectories, foundSubmissions); // Merge everything in a submission set. List submissions = new ArrayList<>(foundSubmissions.values()); @@ -73,7 +79,11 @@ public SubmissionSet buildSubmissionSet() throws ExitException { /** * Verify that the given root directories exist and have no duplicate entries. */ - private Set verifyRootDirectories(List rootDirectoryNames) throws ExitException { + private Set verifyRootDirectories(List rootDirectoryNames, boolean areNewDirectories) throws ExitException { + if (areNewDirectories && rootDirectoryNames.isEmpty()) { + throw new RootDirectoryException("No root directories specified with submissions to check for plagiarism!"); + } + Set canonicalRootDirectories = new HashSet<>(rootDirectoryNames.size()); for (String rootDirectoryName : rootDirectoryNames) { File rootDirectory = new File(rootDirectoryName); @@ -94,17 +104,43 @@ private Set verifyRootDirectories(List rootDirectoryNames) throws return canonicalRootDirectories; } - private Optional loadBaseCode(Set rootDirectories, Map foundSubmissions) throws ExitException { + /** + * Verify that the new and old directory sets are disjunct and modify the old submissions set if necessary. + */ + private void checkForNonOverlappingRootDirectories(Set submissionDirectories, Set oldSubmissionDirectories) { + + Set commonRootdirectories = new HashSet<>(submissionDirectories); + commonRootdirectories.retainAll(oldSubmissionDirectories); + if (commonRootdirectories.isEmpty()) { + return; + } + + // As old submission directories are only read while new submission directories are both read and checked, the + // former use can be removed without affecting the result of the checks. + oldSubmissionDirectories.removeAll(commonRootdirectories); + for (File rootDirectory : commonRootdirectories) { + System.out.println("Warning: Root directory \"" + rootDirectory.toString() + + "\" is specified both for plagiarism checking and for prior submissions, will perform plagiarism checking only."); + } + } + + private Optional loadBaseCode(Set submissionDirectories, Set oldSubmissionDirectories, + Map foundSubmissions) throws ExitException { // Extract the basecode submission if necessary. Optional baseCodeSubmission = Optional.empty(); if (options.hasBaseCode()) { String baseCodeName = options.getBaseCodeSubmissionName().get(); Submission baseCode = loadBaseCodeAsPath(baseCodeName); if (baseCode == null) { - if (rootDirectories.size() > 1) { + int numberOfRootDirectories = submissionDirectories.size() + oldSubmissionDirectories.size(); + if (numberOfRootDirectories > 1) { throw new BasecodeException("The base code submission needs to be specified by path instead of by name!"); } - File rootDirectory = rootDirectories.iterator().next(); + + // There is one root directory, and the submissionDirectories variable has been checked to be non-empty. + // That set thus contains the the one and only root directory. + File rootDirectory = submissionDirectories.iterator().next(); + // Single root-directory, try the legacy way of specifying basecode. baseCode = loadBaseCodeViaName(baseCodeName, rootDirectory, foundSubmissions); } @@ -140,7 +176,7 @@ private Submission loadBaseCodeAsPath(String baseCodeName) throws ExitException try { // Use an unlikely short name for the base code. If all is well, this name should not appear // in the output since basecode matches are removed from it - return processSubmission(basecodeSubmission.getName(), basecodeSubmission); + return processSubmission(basecodeSubmission.getName(), basecodeSubmission, false); } catch (SubmissionException exception) { throw new BasecodeException(exception.getMessage(), exception); // Change thrown exception to basecode exception. } @@ -228,10 +264,11 @@ private String isExcludedEntry(File submissionEntry) { /** * Process the given directory entry as a submission, the path MUST not be excluded. * @param submissionFile the file for the submission. + * @param isNew states whether submissions found in the root directory must be checked for plagiarism. * @return The entry converted to a submission. * @throws ExitException when an error has been found with the entry. */ - private Submission processSubmission(String submissionName, File submissionFile) throws ExitException { + private Submission processSubmission(String submissionName, File submissionFile, boolean isNew) throws ExitException { if (submissionFile.isDirectory() && options.getSubdirectoryName() != null) { // Use subdirectory instead @@ -248,15 +285,17 @@ private Submission processSubmission(String submissionName, File submissionFile) } submissionFile = makeCanonical(submissionFile, it -> new SubmissionException("Cannot create submission: " + submissionName, it)); - return new Submission(submissionName, submissionFile, parseFilesRecursively(submissionFile), language, errorCollector); + return new Submission(submissionName, submissionFile, isNew, parseFilesRecursively(submissionFile), language, errorCollector); } /** * Process entries in the root directory to check whether they qualify as submissions. * @param rootDirectory is the root directory being examined. * @param foundSubmissions Submissions found so far, is updated in-place. + * @param isNew states whether submissions found in the root directory must be checked for plagiarism. */ - private void processRootDirectoryEntries(File rootDirectory, boolean multipleRoots, Map foundSubmissions) throws ExitException { + private void processRootDirectoryEntries(File rootDirectory, boolean multipleRoots, Map foundSubmissions, boolean isNew) + throws ExitException { for (String fileName : listSubmissionFiles(rootDirectory)) { File submissionFile = new File(rootDirectory, fileName); @@ -264,7 +303,7 @@ private void processRootDirectoryEntries(File rootDirectory, boolean multipleRoo if (errorMessage == null) { String rootDirectoryPrefix = multipleRoots ? (rootDirectory.getName() + File.separator) : ""; String submissionName = rootDirectoryPrefix + fileName; - Submission submission = processSubmission(submissionName, submissionFile); + Submission submission = processSubmission(submissionName, submissionFile, isNew); foundSubmissions.put(submission.getRoot(), submission); } else { System.out.println(errorMessage); diff --git a/jplag/src/main/java/de/jplag/options/JPlagOptions.java b/jplag/src/main/java/de/jplag/options/JPlagOptions.java index c260055fc..c40c5b700 100644 --- a/jplag/src/main/java/de/jplag/options/JPlagOptions.java +++ b/jplag/src/main/java/de/jplag/options/JPlagOptions.java @@ -80,9 +80,14 @@ public class JPlagOptions { private Set excludedFiles = Collections.emptySet(); /** - * Directory that contains all submissions. + * Directories with new submissions. These must be checked for plagiarism. */ - private List rootDirectoryNames; + private List submissionDirectories; + + /** + * Directories with old submissions to check against. + */ + private List oldSubmissionDirectories; /** * Path name of the directory containing the base code. @@ -124,8 +129,9 @@ public class JPlagOptions { /** * Constructor with required attributes. */ - public JPlagOptions(List rootDirectoryNames, LanguageOption languageOption) { - this.rootDirectoryNames = rootDirectoryNames; + public JPlagOptions(List submissionDirectories, List oldSubmissionDirectories, LanguageOption languageOption) { + this.submissionDirectories = submissionDirectories; + this.oldSubmissionDirectories = oldSubmissionDirectories; this.languageOption = languageOption; } @@ -165,8 +171,12 @@ public Integer getMinimumTokenMatch() { return minimumTokenMatch; } - public List getRootDirectoryNames() { - return rootDirectoryNames; + public List getSubmissionDirectories() { + return submissionDirectories; + } + + public List getOldSubmissionDirectories() { + return oldSubmissionDirectories; } public SimilarityMetric getSimilarityMetric() { @@ -260,8 +270,12 @@ public void setMinimumTokenMatch(Integer minimumTokenMatch) { } } - public void setRootDirectoryNames(List rootDirectoryNames) { - this.rootDirectoryNames = rootDirectoryNames; + public void setSubmissionDirectories(List submissionDirectories) { + this.submissionDirectories = submissionDirectories; + } + + public void setOldSubmissionDirectories(List oldSubmissionDirectories) { + this.oldSubmissionDirectories = oldSubmissionDirectories; } public void setSimilarityMetric(SimilarityMetric similarityMetric) { diff --git a/jplag/src/main/java/de/jplag/reporting2/reportobject/ReportObjectFactory.java b/jplag/src/main/java/de/jplag/reporting2/reportobject/ReportObjectFactory.java index c4b092861..f27391497 100644 --- a/jplag/src/main/java/de/jplag/reporting2/reportobject/ReportObjectFactory.java +++ b/jplag/src/main/java/de/jplag/reporting2/reportobject/ReportObjectFactory.java @@ -34,7 +34,11 @@ private static OverviewReport generateOverviewReport(JPlagResult result) { List comparisons = result.getComparisons(); OverviewReport overviewReport = new OverviewReport(); - overviewReport.setSubmissionFolderPath(result.getOptions().getRootDirectoryNames()); + // TODO: Consider to treat entries that were checked differently from old entries with prior work. + List folders = new ArrayList<>(); + folders.addAll(result.getOptions().getSubmissionDirectories()); + folders.addAll(result.getOptions().getOldSubmissionDirectories()); + overviewReport.setSubmissionFolderPath(folders); String baseCodePath = result.getOptions().hasBaseCode() ? result.getOptions().getBaseCodeSubmissionName().orElse("") : ""; overviewReport.setBaseCodeFolderPath(baseCodePath); diff --git a/jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java b/jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java index 8d86ffc79..8ef9e7b76 100644 --- a/jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java +++ b/jplag/src/main/java/de/jplag/strategy/AbstractComparisonStrategy.java @@ -1,5 +1,7 @@ package de.jplag.strategy; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import de.jplag.GreedyStringTiling; @@ -47,4 +49,30 @@ protected Optional compareSubmissions(Submission first, Submiss } return Optional.empty(); } + + /** + * @return a list of all submission tuples to be processed. + */ + protected static List buildComparisonTuples(List submissions) { + List tuples = new ArrayList<>(); + + for (int i = 0; i < (submissions.size() - 1); i++) { + Submission first = submissions.get(i); + if (first.getTokenList() == null) { + continue; + } + + for (int j = (i + 1); j < submissions.size(); j++) { + Submission second = submissions.get(j); + if (second.getTokenList() == null) { + continue; + } + + if (first.isNew() || second.isNew()) { + tuples.add(new SubmissionTuple(first, second)); + } + } + } + return tuples; + } } diff --git a/jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java b/jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java index 21793a27e..4df072a9f 100644 --- a/jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java +++ b/jplag/src/main/java/de/jplag/strategy/NormalComparisonStrategy.java @@ -25,22 +25,10 @@ public JPlagResult compareSubmissions(SubmissionSet submissionSet) { List submissions = submissionSet.getSubmissions(); long timeBeforeStartInMillis = System.currentTimeMillis(); - int i, j, numberOfSubmissions = submissions.size(); - Submission first, second; List comparisons = new ArrayList<>(); - for (i = 0; i < (numberOfSubmissions - 1); i++) { - first = submissions.get(i); - if (first.getTokenList() == null) { - continue; - } - for (j = (i + 1); j < numberOfSubmissions; j++) { - second = submissions.get(j); - if (second.getTokenList() == null) { - continue; - } - compareSubmissions(first, second, withBaseCode).ifPresent(comparisons::add); - } + for (SubmissionTuple tuple : buildComparisonTuples(submissions)) { + compareSubmissions(tuple.getLeft(), tuple.getRight(), withBaseCode).ifPresent(comparisons::add); } long durationInMillis = System.currentTimeMillis() - timeBeforeStartInMillis; diff --git a/jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java b/jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java index fc659cbf9..72a5bc054 100644 --- a/jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java +++ b/jplag/src/main/java/de/jplag/strategy/ParallelComparisonStrategy.java @@ -71,25 +71,6 @@ public JPlagResult compareSubmissions(SubmissionSet submissionSet) { return new JPlagResult(comparisons, submissionSet, durationInMillis, options); } - /** - * @return a list of all submission tuples to be processed. - */ - private List buildComparisonTuples(List submissions) { - List tuples = new ArrayList<>(); - for (int i = 0; i < (submissions.size() - 1); i++) { - Submission first = submissions.get(i); - if (first.getTokenList() != null) { - for (int j = (i + 1); j < submissions.size(); j++) { - Submission second = submissions.get(j); - if (second.getTokenList() != null) { - tuples.add(new SubmissionTuple(first, second)); - } - } - } - } - return tuples; - } - /** * Creates a runnable which compares a submission tuple. If the submissions are locked, the runnable is re-submitted. * @param tuple contains the submissions to compare. diff --git a/jplag/src/main/resources/de/jplag/messages.properties b/jplag/src/main/resources/de/jplag/messages.properties index 584ab4d89..7b4d41674 100644 --- a/jplag/src/main/resources/de/jplag/messages.properties +++ b/jplag/src/main/resources/de/jplag/messages.properties @@ -6,7 +6,9 @@ CommandLineArgument.ExcludeFile=All files named in this file will be ignored in CommandLineArgument.Language=Select the language to parse the submissions CommandLineArgument.MinTokenMatch=Tunes the comparison sensitivity by adjusting the minimum token required to be counted as a matching section. A smaller increases the sensitivity but might lead to more false-positives CommandLineArgument.ResultFolder=Name of the directory in which the comparison results will be stored -CommandLineArgument.RootDirectory=Root-directory that contains submissions +CommandLineArgument.RootDirectory=Root-directory with submissions to check for plagiarism +CommandLineArgument.PlagiarismDirectory=Root-directory with submissions to check for plagiarism +CommandLineArgument.PriorDirectory=Root-directory with prior submissions to compare against CommandLineArgument.ShownComparisons=The maximum number of comparisons that will be shown in the generated report, if set to -1 all comparisons will be shown CommandLineArgument.SimilarityThreshold=Comparison similarity threshold [0-100]: All comparisons above this threshold will be saved CommandLineArgument.Subdirectory=Look in directories /*/ for programs diff --git a/jplag/src/test/java/de/jplag/BaseCodeTest.java b/jplag/src/test/java/de/jplag/BaseCodeTest.java index 53fc5c168..48c7bfb52 100644 --- a/jplag/src/test/java/de/jplag/BaseCodeTest.java +++ b/jplag/src/test/java/de/jplag/BaseCodeTest.java @@ -52,7 +52,7 @@ public void testBasecodePathComparison() throws ExitException { @Test(expected = RootDirectoryException.class) public void testInvalidRoot() throws ExitException { - runJPlag("basecode", it -> it.setRootDirectoryNames(List.of("WrongRoot"))); + runJPlag("basecode", it -> it.setSubmissionDirectories(List.of("WrongRoot"))); } @Test(expected = BasecodeException.class) diff --git a/jplag/src/test/java/de/jplag/NormalComparisonTest.java b/jplag/src/test/java/de/jplag/NormalComparisonTest.java index 30ccc5e92..82b331689 100644 --- a/jplag/src/test/java/de/jplag/NormalComparisonTest.java +++ b/jplag/src/test/java/de/jplag/NormalComparisonTest.java @@ -118,4 +118,34 @@ public void testMultiRootDirBasecodeName() throws ExitException { runJPlag(paths, it -> it.setBaseCodeSubmissionName(basecodePath)); fail("No basecode exception was thrown!"); } + + @Test + public void testDisjunctNewAndOldRootDirectories() throws ExitException { + List newDirectories = List.of(getBasePath("SimpleDuplicate")); // 2 submissions + List oldDirectories = List.of(getBasePath("basecode")); // 3 submissions + JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> { + }); + int numberOfExpectedComparison = 1 + 3 * 2; + assertEquals(numberOfExpectedComparison, result.getComparisons().size()); + } + + @Test + public void testOverlappingNewAndOldDirectoriesOverlap() throws ExitException { + List newDirectories = List.of(getBasePath("SimpleDuplicate")); // 2 submissions + List oldDirectories = List.of(getBasePath("SimpleDuplicate")); + JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> { + }); + int numberOfExpectedComparison = 1; + assertEquals(numberOfExpectedComparison, result.getComparisons().size()); + } + + @Test + public void testBasecodeInOldDirectory() throws ExitException { + String basecodePath = getBasePath("basecode", "base"); + List newDirectories = List.of(getBasePath("SimpleDuplicate")); // 2 submissions + List oldDirectories = List.of(getBasePath("basecode")); // 3 - 1 submissions + JPlagResult result = runJPlag(newDirectories, oldDirectories, it -> it.setBaseCodeSubmissionName(basecodePath)); + int numberOfExpectedComparison = 1 + 2 * 2; + assertEquals(numberOfExpectedComparison, result.getComparisons().size()); + } } diff --git a/jplag/src/test/java/de/jplag/TestBase.java b/jplag/src/test/java/de/jplag/TestBase.java index 85752ea9e..1b46ec182 100644 --- a/jplag/src/test/java/de/jplag/TestBase.java +++ b/jplag/src/test/java/de/jplag/TestBase.java @@ -38,11 +38,15 @@ protected JPlagResult runJPlagWithDefaultOptions(String testSampleName) throws E } protected JPlagResult runJPlag(String testSampleName, Consumer customization) throws ExitException { - return runJPlag(List.of(getBasePath(testSampleName)), customization); + return runJPlag(List.of(getBasePath(testSampleName)), List.of(), customization); } - protected JPlagResult runJPlag(List testPaths, Consumer customization) throws ExitException { - JPlagOptions options = new JPlagOptions(testPaths, LanguageOption.JAVA); + protected JPlagResult runJPlag(List newPaths, Consumer customization) throws ExitException { + return runJPlag(newPaths, List.of(), customization); + } + + protected JPlagResult runJPlag(List newPaths, List oldPaths, Consumer customization) throws ExitException { + JPlagOptions options = new JPlagOptions(newPaths, oldPaths, LanguageOption.JAVA); options.setVerbosity(Verbosity.LONG); customization.accept(options); JPlag jplag = new JPlag(options); diff --git a/jplag/src/test/java/de/jplag/cli/OldNewRootDirectoriesArgumentTest.java b/jplag/src/test/java/de/jplag/cli/OldNewRootDirectoriesArgumentTest.java new file mode 100644 index 000000000..2fbd76302 --- /dev/null +++ b/jplag/src/test/java/de/jplag/cli/OldNewRootDirectoriesArgumentTest.java @@ -0,0 +1,55 @@ +package de.jplag.cli; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class OldNewRootDirectoriesArgumentTest extends CommandLineInterfaceTest { + @Test + public void testNoRootDirectories() { + buildOptionsFromCLI(); + + assertEquals(0, options.getSubmissionDirectories().size()); + assertEquals(0, options.getOldSubmissionDirectories().size()); + } + + @Test + public void testTwoRootDirectoryArguments() { + buildOptionsFromCLI("root1", "root2"); + + assertEquals(2, options.getSubmissionDirectories().size()); + assertEquals(0, options.getOldSubmissionDirectories().size()); + } + + @Test + public void testNewOption() { + buildOptionsFromCLI("-new", "root1", "root2"); + + assertEquals(2, options.getSubmissionDirectories().size()); + assertEquals(0, options.getOldSubmissionDirectories().size()); + } + + @Test + public void testDoubleNewOption() { + buildOptionsFromCLI("-new", "root1", "-new", "root2"); + + assertEquals(2, options.getSubmissionDirectories().size()); + assertEquals(0, options.getOldSubmissionDirectories().size()); + } + + @Test + public void testOldOption() { + buildOptionsFromCLI("-old", "root1"); + + assertEquals(0, options.getSubmissionDirectories().size()); + assertEquals(1, options.getOldSubmissionDirectories().size()); + } + + @Test + public void testNewAndOldOption() { + buildOptionsFromCLI("-new", "root1", "-old", "root2"); + + assertEquals(1, options.getSubmissionDirectories().size()); + assertEquals(1, options.getOldSubmissionDirectories().size()); + } +}