EndToEndSuiteTest.java 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. package de.jplag.endtoend;
  2. import static org.junit.jupiter.api.Assertions.assertEquals;
  3. import static org.junit.jupiter.api.Assertions.assertNotNull;
  4. import static org.junit.jupiter.api.Assertions.assertTrue;
  5. import java.io.File;
  6. import java.io.IOException;
  7. import java.util.ArrayList;
  8. import java.util.Collection;
  9. import java.util.LinkedList;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.Set;
  13. import java.util.stream.Collectors;
  14. import org.junit.jupiter.api.DynamicContainer;
  15. import org.junit.jupiter.api.DynamicTest;
  16. import org.junit.jupiter.api.TestFactory;
  17. import com.fasterxml.jackson.databind.ObjectMapper;
  18. import de.jplag.JPlag;
  19. import de.jplag.JPlagComparison;
  20. import de.jplag.JPlagResult;
  21. import de.jplag.Language;
  22. import de.jplag.LanguageLoader;
  23. import de.jplag.endtoend.constants.TestDirectoryConstants;
  24. import de.jplag.endtoend.helper.FileHelper;
  25. import de.jplag.endtoend.helper.TestSuiteHelper;
  26. import de.jplag.endtoend.model.ExpectedResult;
  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. class EndToEndSuiteTest {
  36. private static final double EPSILON = 1E-8;
  37. /**
  38. * Creates the test cases over all language options for which data is available and the current test options.
  39. * @return dynamic test cases across all test data and languages
  40. * @throws IOException is thrown for all problems that may occur while parsing the json file.
  41. */
  42. @TestFactory
  43. Collection<DynamicContainer> dynamicOverAllTest() throws IOException, ExitException {
  44. File resultsDirectory = TestDirectoryConstants.BASE_PATH_TO_RESULT_JSON.toFile();
  45. File[] languageDirectories = resultsDirectory.listFiles(File::isDirectory);
  46. List<DynamicContainer> allTests = new LinkedList<>();
  47. for (File languageDirectory : languageDirectories) {
  48. Language language = LanguageLoader.getLanguage(languageDirectory.getName()).orElseThrow();
  49. File[] resultJsons = languageDirectory.listFiles(file -> !file.isDirectory() && file.getName().endsWith(".json"));
  50. List<DynamicContainer> languageTests = new LinkedList<>();
  51. for (File resultJson : resultJsons) {
  52. List<DynamicContainer> testContainers = new LinkedList<>();
  53. ResultDescription[] results = new ObjectMapper().readValue(resultJson, ResultDescription[].class);
  54. for (var result : results) {
  55. var testCases = generateTestsForResultDescription(resultJson, result, language);
  56. testContainers.add(DynamicContainer.dynamicContainer("MTM: " + result.options().minimumTokenMatch(), testCases));
  57. }
  58. languageTests.add(DynamicContainer.dynamicContainer(FileHelper.getFileNameWithoutFileExtension(resultJson), testContainers));
  59. }
  60. allTests.add(DynamicContainer.dynamicContainer(language.getIdentifier(), languageTests));
  61. }
  62. return allTests;
  63. }
  64. /**
  65. * Generates test cases for each test described in the provided result object.
  66. * @param resultJson is the file of the result json
  67. * @param result is one test suite configuration of the deserialized {@code resultJson}
  68. * @param language is the language to run JPlag with
  69. * @return a collection of test cases, each validating one {@link JPlagResult} against its {@link ExpectedResult}
  70. * counterpart
  71. */
  72. private Collection<DynamicTest> generateTestsForResultDescription(File resultJson, ResultDescription result, Language language)
  73. throws ExitException {
  74. File submissionDirectory = TestSuiteHelper.getSubmissionDirectory(language, resultJson);
  75. JPlagOptions jplagOptions = new JPlagOptions(language, Set.of(submissionDirectory), Set.of())
  76. .withMinimumTokenMatch(result.options().minimumTokenMatch());
  77. JPlagResult jplagResult = new JPlag(jplagOptions).run();
  78. Map<String, JPlagComparison> jPlagComparisons = jplagResult.getAllComparisons().stream()
  79. .collect(Collectors.toMap(it -> TestSuiteHelper.getTestIdentifier(it), it -> it));
  80. assertEquals(result.identifierToResultMap().size(), jPlagComparisons.size(), "different number of results and expected results");
  81. return result.identifierToResultMap().keySet().stream().map(identifier -> {
  82. JPlagComparison comparison = jPlagComparisons.get(identifier);
  83. ExpectedResult expectedResult = result.identifierToResultMap().get(identifier);
  84. return generateTest(identifier, expectedResult, comparison);
  85. }).toList();
  86. }
  87. /**
  88. * Generates a test case validating the passed result by comparing it to the expected result values.
  89. * @param name is the name of the test case.
  90. * @param expectedResult contains all expected result values.
  91. * @param result is the comparison object generated from running JPlag.
  92. */
  93. private DynamicTest generateTest(String name, ExpectedResult expectedResult, JPlagComparison result) {
  94. return DynamicTest.dynamicTest(name, () -> {
  95. assertNotNull(result, "No comparison result could be found");
  96. List<String> validationErrors = new ArrayList<>();
  97. if (areDoublesDifferent(expectedResult.resultSimilarityMinimum(), result.minimalSimilarity())) {
  98. validationErrors.add(formattedValidationError("minimal similarity", String.valueOf(expectedResult.resultSimilarityMinimum()),
  99. String.valueOf(result.minimalSimilarity())));
  100. }
  101. if (areDoublesDifferent(expectedResult.resultSimilarityMaximum(), result.maximalSimilarity())) {
  102. validationErrors.add(formattedValidationError("maximal similarity", String.valueOf(expectedResult.resultSimilarityMaximum()),
  103. String.valueOf(result.maximalSimilarity())));
  104. }
  105. if (expectedResult.resultMatchedTokenNumber() != result.getNumberOfMatchedTokens()) {
  106. validationErrors.add(formattedValidationError("number of matched tokens", String.valueOf(expectedResult.resultMatchedTokenNumber()),
  107. String.valueOf(result.getNumberOfMatchedTokens())));
  108. }
  109. assertTrue(validationErrors.isEmpty(), createValidationErrorOutput(validationErrors));
  110. });
  111. }
  112. private boolean areDoublesDifferent(double d1, double d2) {
  113. return Math.abs(d1 - d2) >= EPSILON;
  114. }
  115. /**
  116. * Creates the display message for a result value validation error.
  117. * @param valueName Name of the failed test object
  118. * @param actualValue actual test value
  119. * @param expectedValue expected test value
  120. */
  121. private String formattedValidationError(String valueName, String actualValue, String expectedValue) {
  122. return valueName + " was " + actualValue + " but expected " + expectedValue;
  123. }
  124. /**
  125. * Creates the display info from the passed failed test results
  126. * @return formatted text for the failed comparative values of the current test
  127. */
  128. private String createValidationErrorOutput(List<String> validationErrors) {
  129. return "There were " + validationErrors.size() + " validation error(s):" + System.lineSeparator()
  130. + String.join(System.lineSeparator(), validationErrors);
  131. }
  132. }