EndToEndSuiteTest.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. package de.jplag.endtoend;
  2. import static org.junit.jupiter.api.Assertions.assertNotNull;
  3. import static org.junit.jupiter.api.Assertions.assertTrue;
  4. import java.io.IOException;
  5. import java.nio.file.Path;
  6. import java.util.ArrayList;
  7. import java.util.Arrays;
  8. import java.util.Collection;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.Map.Entry;
  13. import java.util.Optional;
  14. import java.util.StringJoiner;
  15. import org.junit.jupiter.api.AfterAll;
  16. import org.junit.jupiter.api.DynamicTest;
  17. import org.junit.jupiter.api.TestFactory;
  18. import de.jplag.JPlag;
  19. import de.jplag.JPlagComparison;
  20. import de.jplag.JPlagResult;
  21. import de.jplag.LanguageLoader;
  22. import de.jplag.endtoend.helper.FileHelper;
  23. import de.jplag.endtoend.helper.JsonHelper;
  24. import de.jplag.endtoend.helper.TestSuiteHelper;
  25. import de.jplag.endtoend.model.ExpectedResult;
  26. import de.jplag.endtoend.model.Options;
  27. import de.jplag.endtoend.model.ResultDescription;
  28. import de.jplag.exceptions.ExitException;
  29. import de.jplag.options.JPlagOptions;
  30. /**
  31. * Main test class for end-to-end testing in all language. The test cases aim to detect changes in the detection of
  32. * plagiarism in the Java language and to be able to roughly categorize them. The plagiarism is compared with the
  33. * original class. The results are compared with the results from previous tests and changes are detected.
  34. */
  35. public class EndToEndSuiteTest {
  36. private static final double EPSILON = 1E-8;
  37. // Language -> directory names and Paths
  38. private Map<String, Map<String, Path>> languageToTestCaseMapper;
  39. private List<Options> options;
  40. private static Map<String, List<ResultDescription>> temporaryResultList;
  41. private static List<String> validationErrors;
  42. private static String languageIdentifier;
  43. public EndToEndSuiteTest() throws IOException {
  44. // Loading the test resources
  45. languageToTestCaseMapper = TestSuiteHelper.getAllLanguageResources();
  46. // creating the temporary lists for the test run
  47. validationErrors = new ArrayList<>();
  48. temporaryResultList = new HashMap<>();
  49. // creating options object for the testSuite
  50. setRunOptions();
  51. }
  52. /**
  53. * creating the required options object for the endToEnd tests
  54. */
  55. private void setRunOptions() {
  56. options = new ArrayList<>();
  57. options.add(new Options(1));
  58. options.add(new Options(15));
  59. }
  60. /**
  61. * Creates the result json files based on the current test results after the test run.
  62. * @throws IOException is thrown for all problems that may occur while parsing the json file. This includes both reading
  63. */
  64. @AfterAll
  65. public static void tearDown() throws IOException {
  66. for (var resultDescriptionItem : temporaryResultList.entrySet()) {
  67. JsonHelper.writeJsonModelsToJsonFile(resultDescriptionItem.getValue(), resultDescriptionItem.getKey(), languageIdentifier);
  68. }
  69. }
  70. /**
  71. * Creates the test cases over all language options for which data is available and the current test options.
  72. * @return dynamic test cases across all test data and languages
  73. * @throws IOException is thrown for all problems that may occur while parsing the json file. This includes both reading
  74. */
  75. @TestFactory
  76. Collection<DynamicTest> dynamicOverAllTest() throws IOException {
  77. for (Entry<String, Map<String, Path>> languageMap : languageToTestCaseMapper.entrySet()) {
  78. String currentLanguageIdentifier = languageMap.getKey();
  79. for (Entry<String, Path> languagePaths : languageMap.getValue().entrySet()) {
  80. String[] fileNames = FileHelper.loadAllTestFileNames(languagePaths.getValue());
  81. var testCases = TestSuiteHelper.getTestCases(fileNames, languagePaths.getValue());
  82. var testCollection = new ArrayList<DynamicTest>();
  83. String directoryName = languagePaths.getValue().getFileName().toString();
  84. List<ResultDescription> tempResult = JsonHelper.getJsonModelListFromPath(directoryName, currentLanguageIdentifier);
  85. languageIdentifier = currentLanguageIdentifier;
  86. for (Options option : options) {
  87. for (var testCase : testCases) {
  88. Optional<ResultDescription> currentResultDescription = tempResult.stream().filter(x -> x.options().equals(option))
  89. .findFirst();
  90. testCollection.add(DynamicTest.dynamicTest(getTestCaseDisplayName(option, languageIdentifier, testCase), () -> {
  91. runTests(directoryName, option, currentLanguageIdentifier, testCase, currentResultDescription);
  92. }));
  93. }
  94. }
  95. return testCollection;
  96. }
  97. }
  98. return null;
  99. }
  100. /**
  101. * Superordinate test function to be able to continue to check all data to be tested in case of failed tests
  102. * @param directoryName name of the current tested directory
  103. * @param option for the current test run
  104. * @param currentLanguageIdentifier current JPlag language option
  105. * @param testFiles files to be tested
  106. * @param currentResultDescription results stored for the test data
  107. * @throws IOException Signals that an I/O exception of some sort has occurred. Thisclass is the general class of
  108. * exceptions produced by failed orinterrupted I/O operations
  109. * @throws ExitException Exceptions for problems during the execution of JPlag that lead to an preemptive exit.
  110. */
  111. private void runTests(String directoryName, Options option, String currentLanguageIdentifier, String[] testFiles,
  112. Optional<ResultDescription> currentResultDescription) throws IOException, ExitException {
  113. try {
  114. ResultDescription currentResult = currentResultDescription.orElse(null);
  115. runJPlagTestSuite(directoryName, option, currentLanguageIdentifier, testFiles, currentResult);
  116. } finally {
  117. validationErrors.clear();
  118. TestSuiteHelper.clear();
  119. }
  120. }
  121. /**
  122. * EndToEnd test for the passed objects
  123. * @param directoryName name of the current tested directory
  124. * @param options for the current test run
  125. * @param languageIdentifier current JPlag language option
  126. * @param testFiles files to be tested
  127. * @param currentResultDescription results stored for the test data
  128. * @throws IOException Signals that an I/O exception of some sort has occurred. Thisclass is the general class of
  129. * exceptions produced by failed orinterrupted I/O operations
  130. * @throws ExitException Exceptions for problems during the execution of JPlag that lead to an preemptive exit.
  131. */
  132. private void runJPlagTestSuite(String directoryName, Options options, String languageIdentifier, String[] testFiles,
  133. ResultDescription currentResultDescription) throws IOException, ExitException {
  134. String[] submissionPath = FileHelper.createNewTestCaseDirectory(testFiles);
  135. var language = LanguageLoader.getLanguage(languageIdentifier).orElseThrow();
  136. JPlagOptions jplagOptions = new JPlagOptions(language, Arrays.asList(submissionPath), new ArrayList<>())
  137. .withMinimumTokenMatch(options.minimumTokenMatch());
  138. JPlagResult jplagResult = new JPlag(jplagOptions).run();
  139. List<JPlagComparison> currentJPlagComparison = jplagResult.getAllComparisons();
  140. for (JPlagComparison jPlagComparison : currentJPlagComparison) {
  141. String identifier = TestSuiteHelper.getTestIdentifier(jPlagComparison);
  142. addToTemporaryResultMap(directoryName, options, jPlagComparison, languageIdentifier);
  143. assertNotNull(currentResultDescription, "No stored result could be found for the current LanguageOption! " + options);
  144. ExpectedResult result = currentResultDescription.getExpectedResultByIdentifier(TestSuiteHelper.getTestIdentifier(jPlagComparison));
  145. assertNotNull(result, "No stored result could be found for the identifier! " + identifier);
  146. if (areDoublesDifferent(result.resultSimilarityMinimum(), jPlagComparison.minimalSimilarity())) {
  147. addToValidationErrors("minimalSimilarity", String.valueOf(result.resultSimilarityMinimum()),
  148. String.valueOf(jPlagComparison.minimalSimilarity()));
  149. }
  150. if (areDoublesDifferent(result.resultSimilarityMaximum(), jPlagComparison.maximalSimilarity())) {
  151. addToValidationErrors("maximalSimilarity", String.valueOf(result.resultSimilarityMaximum()),
  152. String.valueOf(jPlagComparison.maximalSimilarity()));
  153. }
  154. if (result.resultMatchedTokenNumber() != jPlagComparison.getNumberOfMatchedTokens()) {
  155. addToValidationErrors("numberOfMatchedTokens", String.valueOf(result.resultMatchedTokenNumber()),
  156. String.valueOf(jPlagComparison.getNumberOfMatchedTokens()));
  157. }
  158. assertTrue(validationErrors.isEmpty(), createValidationErrorOutout());
  159. }
  160. }
  161. private boolean areDoublesDifferent(double d1, double d2) {
  162. return Math.abs(d1 - d2) >= EPSILON;
  163. }
  164. /**
  165. * Creates the display message for failed tests
  166. * @param valueName Name of the failed test object
  167. * @param currentValue current test values
  168. * @param expectedValue expected test values
  169. */
  170. private void addToValidationErrors(String valueName, String currentValue, String expectedValue) {
  171. validationErrors.add(valueName + " was " + currentValue + " but expected " + expectedValue);
  172. }
  173. /**
  174. * Creates the display info from the current failed test results
  175. * @return formatted text for the failed comparative values of the current test
  176. */
  177. private String createValidationErrorOutout() {
  178. StringJoiner joiner = new StringJoiner(System.lineSeparator());
  179. joiner.add(""); // empty line at start
  180. joiner.add("There were <" + validationErrors.size() + "> validation error(s):");
  181. validationErrors.stream().forEach(errorLine -> joiner.add(errorLine));
  182. joiner.add(""); // line break at the end
  183. return joiner.toString();
  184. }
  185. /**
  186. * Add or create the current test results in a temporary list to be able to save them later.
  187. * @param directoryName name of the current tested directory
  188. * @param options for the current test run
  189. * @param jPlagComparison current test results
  190. * @param languageIdentifier current JPlag language option
  191. */
  192. private void addToTemporaryResultMap(String directoryName, Options options, JPlagComparison jPlagComparison, String languageIdentifier) {
  193. var element = temporaryResultList.get(directoryName);
  194. if (element != null) {
  195. for (var item : element) {
  196. if (item.options().equals(options)) {
  197. item.putIdentifierToResultMap(TestSuiteHelper.getTestIdentifier(jPlagComparison), new ExpectedResult(
  198. jPlagComparison.minimalSimilarity(), jPlagComparison.maximalSimilarity(), jPlagComparison.getNumberOfMatchedTokens()));
  199. return;
  200. }
  201. }
  202. Map<String, ExpectedResult> temporaryHashMap = new HashMap<>();
  203. temporaryHashMap.put(TestSuiteHelper.getTestIdentifier(jPlagComparison), new ExpectedResult(jPlagComparison.minimalSimilarity(),
  204. jPlagComparison.maximalSimilarity(), jPlagComparison.getNumberOfMatchedTokens()));
  205. element.add(new ResultDescription(languageIdentifier, options, temporaryHashMap));
  206. } else {
  207. var temporaryNewResultList = new ArrayList<ResultDescription>();
  208. Map<String, ExpectedResult> temporaryHashMap = new HashMap<>();
  209. temporaryHashMap.put(TestSuiteHelper.getTestIdentifier(jPlagComparison), new ExpectedResult(jPlagComparison.minimalSimilarity(),
  210. jPlagComparison.maximalSimilarity(), jPlagComparison.getNumberOfMatchedTokens()));
  211. temporaryNewResultList.add(new ResultDescription(languageIdentifier, options, temporaryHashMap));
  212. temporaryResultList.put(directoryName, temporaryNewResultList);
  213. }
  214. }
  215. /**
  216. * Creates the name of the test for better assignment and readability Pattern: Language: (option values)
  217. * filename1-filename2
  218. * @param option under which the current test run
  219. * @param languageIdentifier current language used in the test
  220. * @param testFiles test data for assigning by filename
  221. * @return display name for the individual tests
  222. */
  223. private String getTestCaseDisplayName(Options option, String languageIdentifier, String[] testFiles) {
  224. StringBuilder stringBuilder = new StringBuilder();
  225. stringBuilder.append("(" + String.valueOf(option.minimumTokenMatch()) + ")");
  226. for (int counter = 0; counter < testFiles.length; counter++) {
  227. String fileName = Path.of(testFiles[counter]).toFile().getName();
  228. stringBuilder.append(fileName.substring(0, fileName.lastIndexOf('.')));
  229. if (counter + 1 < testFiles.length) {
  230. stringBuilder.append("-");
  231. }
  232. }
  233. return languageIdentifier + ": " + stringBuilder.toString();
  234. }
  235. }