diff --git a/src/main/java/reposense/RepoSense.java b/src/main/java/reposense/RepoSense.java index 763329e289..fcc02cba00 100644 --- a/src/main/java/reposense/RepoSense.java +++ b/src/main/java/reposense/RepoSense.java @@ -80,7 +80,7 @@ public static void main(String[] args) { cliArguments.isSinceDateProvided(), cliArguments.isUntilDateProvided(), cliArguments.getNumCloningThreads(), cliArguments.getNumAnalysisThreads(), TimeUtil::getElapsedTime, cliArguments.getZoneId(), cliArguments.isFreshClonePerformed(), - cliArguments.isAuthorshipAnalyzed()); + cliArguments.isAuthorshipAnalyzed(), cliArguments.getOriginalityThreshold()); FileUtil.zipFoldersAndFiles(reportFoldersAndFiles, cliArguments.getOutputFilePath().toAbsolutePath(), ".json"); diff --git a/src/main/java/reposense/authorship/AuthorshipReporter.java b/src/main/java/reposense/authorship/AuthorshipReporter.java index 343aeb9af5..25cb0b76d8 100644 --- a/src/main/java/reposense/authorship/AuthorshipReporter.java +++ b/src/main/java/reposense/authorship/AuthorshipReporter.java @@ -30,9 +30,11 @@ public class AuthorshipReporter { /** * Generates and returns the authorship summary for each repo in {@code config}. - * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true. + * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true, based on + * {code originalityThreshold}. */ - public AuthorshipSummary generateAuthorshipSummary(RepoConfiguration config, boolean shouldAnalyzeAuthorship) { + public AuthorshipSummary generateAuthorshipSummary(RepoConfiguration config, boolean shouldAnalyzeAuthorship, + double originalityThreshold) { List textFileInfos = fileInfoExtractor.extractTextFileInfos(config); int numFiles = textFileInfos.size(); @@ -45,7 +47,8 @@ public AuthorshipSummary generateAuthorshipSummary(RepoConfiguration config, boo } List fileResults = textFileInfos.stream() - .map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo, shouldAnalyzeAuthorship)) + .map(fileInfo -> fileInfoAnalyzer.analyzeTextFile(config, fileInfo, shouldAnalyzeAuthorship, + originalityThreshold)) .filter(Objects::nonNull) .collect(Collectors.toList()); diff --git a/src/main/java/reposense/authorship/FileInfoAnalyzer.java b/src/main/java/reposense/authorship/FileInfoAnalyzer.java index 748de08a16..64ccd5e1aa 100644 --- a/src/main/java/reposense/authorship/FileInfoAnalyzer.java +++ b/src/main/java/reposense/authorship/FileInfoAnalyzer.java @@ -1,5 +1,7 @@ package reposense.authorship; +import static reposense.parser.ArgsParser.DEFAULT_ORIGINALITY_THRESHOLD; + import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -45,11 +47,13 @@ public class FileInfoAnalyzer { /** * Analyzes the lines of the file, given in the {@code fileInfo}, that has changed in the time period provided * by {@code config}. - * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true. + * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true, based on + * {@code originalityThreshold}. * Returns null if the file is missing from the local system, or none of the * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ - public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo, boolean shouldAnalyzeAuthorship) { + public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo, boolean shouldAnalyzeAuthorship, + double originalityThreshold) { String relativePath = fileInfo.getPath(); if (Files.notExists(Paths.get(config.getRepoRoot(), relativePath))) { @@ -61,7 +65,7 @@ public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo, b return null; } - aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo, shouldAnalyzeAuthorship); + aggregateBlameAuthorModifiedAndDateInfo(config, fileInfo, shouldAnalyzeAuthorship, originalityThreshold); fileInfo.setFileType(config.getFileType(fileInfo.getPath())); AnnotatorAnalyzer.aggregateAnnotationAuthorInfo(fileInfo, config.getAuthorConfig(), shouldAnalyzeAuthorship); @@ -83,7 +87,7 @@ public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo, b * {@link Author} specified in {@code config} contributed to the file in {@code fileInfo}. */ public FileResult analyzeTextFile(RepoConfiguration config, FileInfo fileInfo) { - return analyzeTextFile(config, fileInfo, false); + return analyzeTextFile(config, fileInfo, false, DEFAULT_ORIGINALITY_THRESHOLD); } /** @@ -153,10 +157,11 @@ private FileResult generateBinaryFileResult(RepoConfiguration config, FileInfo f * The {@code config} is used to obtain the root directory for running git blame as well as other parameters used * in determining which author to assign to each line and whether to set the last modified date for a * {@code lineInfo}. - * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true. + * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true, based on + * {@code originalityThreshold}. */ private void aggregateBlameAuthorModifiedAndDateInfo(RepoConfiguration config, FileInfo fileInfo, - boolean shouldAnalyzeAuthorship) { + boolean shouldAnalyzeAuthorship, double originalityThreshold) { String blameResults; if (!config.isFindingPreviousAuthorsPerformed()) { @@ -199,7 +204,7 @@ private void aggregateBlameAuthorModifiedAndDateInfo(RepoConfiguration config, F if (shouldAnalyzeAuthorship && !author.equals(Author.UNKNOWN_AUTHOR)) { String lineContent = fileInfo.getLine(lineCount / 5 + 1).getContent(); boolean isFullCredit = AuthorshipAnalyzer.analyzeAuthorship(config, fileInfo.getPath(), lineContent, - commitHash, author); + commitHash, author, originalityThreshold); fileInfo.setIsFullCredit(lineCount / 5, isFullCredit); } } diff --git a/src/main/java/reposense/authorship/analyzer/AuthorshipAnalyzer.java b/src/main/java/reposense/authorship/analyzer/AuthorshipAnalyzer.java index c19a8b7ab4..498ccd0162 100644 --- a/src/main/java/reposense/authorship/analyzer/AuthorshipAnalyzer.java +++ b/src/main/java/reposense/authorship/analyzer/AuthorshipAnalyzer.java @@ -22,9 +22,6 @@ */ public class AuthorshipAnalyzer { private static final Logger logger = LogsManager.getLogger(AuthorshipAnalyzer.class); - - private static final double ORIGINALITY_THRESHOLD = 0.51; - private static final String DIFF_FILE_CHUNK_SEPARATOR = "\ndiff --git a/.*\n"; private static final Pattern FILE_CHANGED_PATTERN = Pattern.compile("\n(-){3} a?/(?.*)\n(\\+){3} b?/(?.*)\n"); @@ -45,11 +42,11 @@ public class AuthorshipAnalyzer { private static final String DELETED_LINE_SYMBOL = "-"; /** - * Analyzes the authorship of {@code lineContent} in {@code filePath}. + * Analyzes the authorship of {@code lineContent} in {@code filePath} based on {@code originalityThreshold}. * Returns {@code true} if {@code currentAuthor} should be assigned full credit, {@code false} otherwise. */ public static boolean analyzeAuthorship(RepoConfiguration config, String filePath, String lineContent, - String commitHash, Author currentAuthor) { + String commitHash, Author currentAuthor, double originalityThreshold) { // Empty lines are ignored and given full credit if (lineContent.isEmpty()) { return true; @@ -58,7 +55,7 @@ public static boolean analyzeAuthorship(RepoConfiguration config, String filePat CandidateLine deletedLine = getDeletedLineWithLowestOriginality(config, filePath, lineContent, commitHash); // Give full credit if there are no deleted lines found or deleted line is more than originality threshold - if (deletedLine == null || deletedLine.getOriginalityScore() > ORIGINALITY_THRESHOLD) { + if (deletedLine == null || deletedLine.getOriginalityScore() > originalityThreshold) { return true; } @@ -80,7 +77,7 @@ public static boolean analyzeAuthorship(RepoConfiguration config, String filePat // Check the previous version as currentAuthor is the same as author of the previous version return analyzeAuthorship(config, deletedLine.getFilePath(), deletedLine.getLineContent(), - deletedLineInfo.getCommitHash(), deletedLineInfo.getAuthor()); + deletedLineInfo.getCommitHash(), deletedLineInfo.getAuthor(), originalityThreshold); } /** diff --git a/src/main/java/reposense/model/CliArguments.java b/src/main/java/reposense/model/CliArguments.java index 89bc2200ec..1a8a2cb80c 100644 --- a/src/main/java/reposense/model/CliArguments.java +++ b/src/main/java/reposense/model/CliArguments.java @@ -36,6 +36,7 @@ public class CliArguments { private final ZoneId zoneId; private final boolean isFindingPreviousAuthorsPerformed; private final boolean isAuthorshipAnalyzed; + private final double originalityThreshold; private boolean isTestMode = ArgsParser.DEFAULT_IS_TEST_MODE; private boolean isFreshClonePerformed = ArgsParser.DEFAULT_SHOULD_FRESH_CLONE; @@ -81,6 +82,7 @@ private CliArguments(Builder builder) { this.reportConfigFilePath = builder.reportConfigFilePath; this.reportConfiguration = builder.reportConfiguration; this.isAuthorshipAnalyzed = builder.isAuthorshipAnalyzed; + this.originalityThreshold = builder.originalityThreshold; } public ZoneId getZoneId() { @@ -195,6 +197,10 @@ public boolean isAuthorshipAnalyzed() { return isAuthorshipAnalyzed; } + public double getOriginalityThreshold() { + return originalityThreshold; + } + @Override public boolean equals(Object other) { // short circuit if same object @@ -233,7 +239,8 @@ public boolean equals(Object other) { && Objects.equals(this.authorConfigFilePath, otherCliArguments.authorConfigFilePath) && Objects.equals(this.groupConfigFilePath, otherCliArguments.groupConfigFilePath) && Objects.equals(this.reportConfigFilePath, otherCliArguments.reportConfigFilePath) - && this.isAuthorshipAnalyzed == otherCliArguments.isAuthorshipAnalyzed; + && this.isAuthorshipAnalyzed == otherCliArguments.isAuthorshipAnalyzed + && Objects.equals(this.originalityThreshold, otherCliArguments.originalityThreshold); } /** @@ -268,6 +275,7 @@ public static final class Builder { private Path reportConfigFilePath; private ReportConfiguration reportConfiguration; private boolean isAuthorshipAnalyzed; + private double originalityThreshold; public Builder() { } @@ -520,6 +528,16 @@ public Builder isAuthorshipAnalyzed(boolean isAuthorshipAnalyzed) { return this; } + /** + * Adds the {@code originalityThreshold} to CliArguments. + * + * @param originalityThreshold the originality threshold. + */ + public Builder originalityThreshold(double originalityThreshold) { + this.originalityThreshold = originalityThreshold; + return this; + } + /** * Builds CliArguments. * diff --git a/src/main/java/reposense/parser/ArgsParser.java b/src/main/java/reposense/parser/ArgsParser.java index e0f42694b3..9ce37f7230 100644 --- a/src/main/java/reposense/parser/ArgsParser.java +++ b/src/main/java/reposense/parser/ArgsParser.java @@ -40,6 +40,7 @@ public class ArgsParser { public static final int DEFAULT_NUM_ANALYSIS_THREADS = Runtime.getRuntime().availableProcessors(); public static final boolean DEFAULT_IS_TEST_MODE = false; public static final boolean DEFAULT_SHOULD_FRESH_CLONE = false; + public static final double DEFAULT_ORIGINALITY_THRESHOLD = 0.51; public static final String[] HELP_FLAGS = new String[] {"--help", "-h"}; public static final String[] CONFIG_FLAGS = new String[] {"--config", "-c"}; @@ -63,6 +64,7 @@ public class ArgsParser { public static final String[] TEST_MODE_FLAG = new String[] {"--test-mode"}; public static final String[] FRESH_CLONING_FLAG = new String[] {"--fresh-cloning"}; public static final String[] ANALYZE_AUTHORSHIP_FLAGS = new String[] {"--analyze-authorship", "-A"}; + public static final String[] ORIGINALITY_THRESHOLD_FLAGS = new String[] {"--originality-threshold", "-ot"}; private static final Logger logger = LogsManager.getLogger(ArgsParser.class); @@ -201,6 +203,13 @@ private static ArgumentParser getArgumentParser() { .action(Arguments.storeTrue()) .help("A flag to perform analysis of code authorship."); + parser.addArgument(ORIGINALITY_THRESHOLD_FLAGS) + .dest(ORIGINALITY_THRESHOLD_FLAGS[0]) + .metavar("(0.0 ~ 1.0)") + .type(new OriginalityThresholdArgumentType()) + .setDefault(DEFAULT_ORIGINALITY_THRESHOLD) + .help("The originality threshold for analysis of code authorship."); + // Mutex flags - these will always be the last parameters in help message. mutexParser.addArgument(CONFIG_FLAGS) .dest(CONFIG_FLAGS[0]) @@ -280,6 +289,7 @@ public static CliArguments parse(String[] args) throws HelpScreenException, Pars boolean shouldFindPreviousAuthors = results.get(FIND_PREVIOUS_AUTHORS_FLAGS[0]); boolean isTestMode = results.get(TEST_MODE_FLAG[0]); boolean isAuthorshipAnalyzed = results.get(ANALYZE_AUTHORSHIP_FLAGS[0]); + double originalityThreshold = results.get(ORIGINALITY_THRESHOLD_FLAGS[0]); int numCloningThreads = results.get(CLONING_THREADS_FLAG[0]); int numAnalysisThreads = results.get(ANALYSIS_THREADS_FLAG[0]); @@ -299,7 +309,8 @@ public static CliArguments parse(String[] args) throws HelpScreenException, Pars .numCloningThreads(numCloningThreads) .numAnalysisThreads(numAnalysisThreads) .isTestMode(isTestMode) - .isAuthorshipAnalyzed(isAuthorshipAnalyzed); + .isAuthorshipAnalyzed(isAuthorshipAnalyzed) + .originalityThreshold(originalityThreshold); LogsManager.setLogFolderLocation(outputFolderPath); diff --git a/src/main/java/reposense/parser/OriginalityThresholdArgumentType.java b/src/main/java/reposense/parser/OriginalityThresholdArgumentType.java new file mode 100644 index 0000000000..a94c6713a0 --- /dev/null +++ b/src/main/java/reposense/parser/OriginalityThresholdArgumentType.java @@ -0,0 +1,25 @@ +package reposense.parser; + +import net.sourceforge.argparse4j.inf.Argument; +import net.sourceforge.argparse4j.inf.ArgumentParser; +import net.sourceforge.argparse4j.inf.ArgumentParserException; +import net.sourceforge.argparse4j.inf.ArgumentType; + +/** + * Verifies and parses a string-formatted double, between 0.0 and 1.0, to an {@link Double} object. + */ +public class OriginalityThresholdArgumentType implements ArgumentType { + private static final String PARSE_EXCEPTION_MESSAGE_THRESHOLD_OUT_OF_BOUND = + "Invalid threshold. It must be a number between 0.0 and 1.0."; + + @Override + public Double convert(ArgumentParser parser, Argument arg, String value) throws ArgumentParserException { + double threshold = Double.parseDouble(value); + + if (Double.compare(threshold, 0.0) < 0 || Double.compare(threshold, 1.0) > 0) { + throw new ArgumentParserException(PARSE_EXCEPTION_MESSAGE_THRESHOLD_OUT_OF_BOUND, parser); + } + + return threshold; + } +} diff --git a/src/main/java/reposense/report/ReportGenerator.java b/src/main/java/reposense/report/ReportGenerator.java index 23b4cbe7a2..81a95bc17d 100644 --- a/src/main/java/reposense/report/ReportGenerator.java +++ b/src/main/java/reposense/report/ReportGenerator.java @@ -111,6 +111,7 @@ public class ReportGenerator { * @param zoneId The timezone to adjust all date-times to. * @param shouldFreshClone The boolean variable for whether to clone a repo again during tests. * @param shouldAnalyzeAuthorship The boolean variable for whether to further analyze authorship. + * @param originalityThreshold The double variable for originality threshold in analyze authorship. * @return the list of file paths that were generated. * @throws IOException if templateZip.zip does not exist in jar file. */ @@ -118,7 +119,7 @@ public List generateReposReport(List configs, String ou ReportConfiguration reportConfig, String generationDate, LocalDateTime cliSinceDate, LocalDateTime untilDate, boolean isSinceDateProvided, boolean isUntilDateProvided, int numCloningThreads, int numAnalysisThreads, Supplier reportGenerationTimeProvider, ZoneId zoneId, - boolean shouldFreshClone, boolean shouldAnalyzeAuthorship) throws IOException { + boolean shouldFreshClone, boolean shouldAnalyzeAuthorship, double originalityThreshold) throws IOException { prepareTemplateFile(outputPath); if (Files.exists(Paths.get(assetsPath))) { FileUtil.copyDirectoryContents(assetsPath, outputPath, assetsFilesWhiteList); @@ -127,8 +128,8 @@ public List generateReposReport(List configs, String ou earliestSinceDate = null; progressTracker = new ProgressTracker(configs.size()); - List reportFoldersAndFiles = cloneAndAnalyzeRepos(configs, outputPath, - numCloningThreads, numAnalysisThreads, shouldFreshClone, shouldAnalyzeAuthorship); + List reportFoldersAndFiles = cloneAndAnalyzeRepos(configs, outputPath, numCloningThreads, + numAnalysisThreads, shouldFreshClone, shouldAnalyzeAuthorship, originalityThreshold); LocalDateTime reportSinceDate = (TimeUtil.isEqualToArbitraryFirstDateConverted(cliSinceDate, zoneId)) ? earliestSinceDate : cliSinceDate; @@ -186,12 +187,14 @@ private Map> groupConfigsByRepoLocation( * To turn off multi-threading, run the program with the flags * {@code --cloning-threads 1 --analysis-threads 1}. * For test environments, cloning is skipped if it has been done before and {@code shouldFreshClone} is false. - * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true. + * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true, based on + * {@code originalityThreshold}. * * @return A list of paths to the JSON report files generated for each repository. */ private List cloneAndAnalyzeRepos(List configs, String outputPath, int numCloningThreads, - int numAnalysisThreads, boolean shouldFreshClone, boolean shouldAnalyzeAuthorship) { + int numAnalysisThreads, boolean shouldFreshClone, boolean shouldAnalyzeAuthorship, + double originalityThreshold) { Map> repoLocationMap = groupConfigsByRepoLocation(configs); List repoLocationList = new ArrayList<>(repoLocationMap.keySet()); @@ -215,7 +218,7 @@ private List cloneAndAnalyzeRepos(List configs, String // for analysis is no more than `numAnalysisThreads`. CompletableFuture analyzeFuture = cloneFuture.thenApplyAsync( cloneJobOutput -> analyzeRepos(outputPath, configsToAnalyze, cloneJobOutput, - shouldAnalyzeAuthorship), + shouldAnalyzeAuthorship, originalityThreshold), analyzeExecutor); analyzeJobFutures.add(analyzeFuture); @@ -276,13 +279,14 @@ private CloneJobOutput cloneRepo(RepoConfiguration config, RepoLocation location /** * Analyzes all repos in {@code configsToAnalyze} and generates their report at {@code outputPath}. * Uses {@code cloneJobOutput} to find repo location, default branch and whether cloning was successful. - * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true. + * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true, based on + * {@code originalityThreshold}. * * @return An {@link AnalyzeJobOutput} object comprising the {@code location} of the repo, whether the cloning was * successful, the list of {@code generatedFiles} by the analysis and a list of {@code analysisErrors} encountered. */ private AnalyzeJobOutput analyzeRepos(String outputPath, List configsToAnalyze, - CloneJobOutput cloneJobOutput, boolean shouldAnalyzeAuthorship) { + CloneJobOutput cloneJobOutput, boolean shouldAnalyzeAuthorship, double originalityThreshold) { RepoLocation location = cloneJobOutput.getLocation(); boolean cloneSuccessful = cloneJobOutput.isCloneSuccessful(); List generatedFiles = new ArrayList<>(); @@ -308,7 +312,7 @@ private AnalyzeJobOutput analyzeRepos(String outputPath, List FileUtil.createDirectory(repoReportDirectory); generatedFiles.addAll(analyzeRepo(configToAnalyze, repoReportDirectory.toString(), - shouldAnalyzeAuthorship)); + shouldAnalyzeAuthorship, originalityThreshold)); } catch (IOException ioe) { String logMessage = String.format(MESSAGE_ERROR_CREATING_DIRECTORY, configToAnalyze.getLocation(), configToAnalyze.getBranch()); @@ -341,13 +345,14 @@ private AnalyzeJobOutput analyzeRepos(String outputPath, List /** * Analyzes repo specified by {@code config} and generates the report at {@code repoReportDirectory}. - * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true. + * Further analyzes the authorship of each line in the commit if {@code shouldAnalyzeAuthorship} is true, based on + * {@code originalityThreshold}. * * @return A list of paths to the JSON report files generated for the repo specified by {@code config}. * @throws NoAuthorsWithCommitsFoundException if there are no authors with commits found for the repo. */ private List analyzeRepo(RepoConfiguration config, String repoReportDirectory, - boolean shouldAnalyzeAuthorship) throws NoAuthorsWithCommitsFoundException { + boolean shouldAnalyzeAuthorship, double originalityThreshold) throws NoAuthorsWithCommitsFoundException { // preprocess the config and repo updateRepoConfig(config); updateAuthorList(config); @@ -359,7 +364,7 @@ private List analyzeRepo(RepoConfiguration config, String repoReportDirect AuthorshipReporter authorshipReporter = new AuthorshipReporter(); AuthorshipSummary authorshipSummary = authorshipReporter.generateAuthorshipSummary(config, - shouldAnalyzeAuthorship); + shouldAnalyzeAuthorship, originalityThreshold); CommitsReporter commitsReporter = new CommitsReporter(); CommitContributionSummary commitSummary = commitsReporter.generateCommitSummary(config); diff --git a/src/test/java/reposense/authorship/AuthorshipAnalyzerTest.java b/src/test/java/reposense/authorship/AuthorshipAnalyzerTest.java index f31f143a7d..7aad0398c7 100644 --- a/src/test/java/reposense/authorship/AuthorshipAnalyzerTest.java +++ b/src/test/java/reposense/authorship/AuthorshipAnalyzerTest.java @@ -1,5 +1,7 @@ package reposense.authorship; +import static reposense.parser.ArgsParser.DEFAULT_ORIGINALITY_THRESHOLD; + import java.time.LocalDateTime; import java.time.Month; import java.util.Arrays; @@ -174,7 +176,7 @@ private FileInfo analyzeTextFile(String relativePath) { FileInfo fileInfo = fileInfoExtractor.generateFileInfo(config, relativePath); FileInfoAnalyzer fileInfoAnalyzer = new FileInfoAnalyzer(); - fileInfoAnalyzer.analyzeTextFile(config, fileInfo, true); + fileInfoAnalyzer.analyzeTextFile(config, fileInfo, true, DEFAULT_ORIGINALITY_THRESHOLD); return fileInfo; } diff --git a/src/test/java/reposense/parser/OriginalityThresholdArgumentTypeTest.java b/src/test/java/reposense/parser/OriginalityThresholdArgumentTypeTest.java new file mode 100644 index 0000000000..5b3cff7439 --- /dev/null +++ b/src/test/java/reposense/parser/OriginalityThresholdArgumentTypeTest.java @@ -0,0 +1,31 @@ +package reposense.parser; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import net.sourceforge.argparse4j.inf.ArgumentParserException; + +public class OriginalityThresholdArgumentTypeTest { + + @Test + public void originalityThresholdArgumentType_convertValidInput_success() throws ArgumentParserException { + Assertions.assertEquals(0, convert("0")); + Assertions.assertEquals(0.0001, convert("0.0001")); + Assertions.assertEquals(0.5, convert("0.5")); + Assertions.assertEquals(0.9999, convert("0.9999")); + Assertions.assertEquals(1, convert("1")); + } + + @Test + public void originalityThresholdArgumentType_convertOutOfBoundInput_throwException() { + Assertions.assertThrowsExactly(ArgumentParserException.class, () -> convert("-10.123")); + Assertions.assertThrowsExactly(ArgumentParserException.class, () -> convert("-0.0001")); + Assertions.assertThrowsExactly(ArgumentParserException.class, () -> convert("1.0001")); + Assertions.assertThrowsExactly(ArgumentParserException.class, () -> convert("10.123")); + } + + private double convert(String input) throws ArgumentParserException { + OriginalityThresholdArgumentType converter = new OriginalityThresholdArgumentType(); + return converter.convert(null, null, input); + } +}