RFrontendTest.java 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package de.jplag.R;
  2. import static org.junit.jupiter.api.Assertions.assertArrayEquals;
  3. import static org.junit.jupiter.api.Assertions.fail;
  4. import java.io.*;
  5. import java.nio.file.Files;
  6. import java.nio.file.Path;
  7. import java.util.List;
  8. import java.util.stream.IntStream;
  9. import java.util.stream.StreamSupport;
  10. import org.junit.jupiter.api.BeforeEach;
  11. import org.junit.jupiter.api.Test;
  12. import org.slf4j.Logger;
  13. import org.slf4j.LoggerFactory;
  14. import de.jplag.Token;
  15. import de.jplag.TokenConstants;
  16. import de.jplag.TokenList;
  17. import de.jplag.TokenPrinter;
  18. import de.jplag.testutils.TestErrorConsumer;
  19. public class RFrontendTest {
  20. /**
  21. * Regular expression for lines that contain no code.
  22. */
  23. private static final String R_NO_CODE_LINE = "\s*(?:#.*)?";
  24. /**
  25. * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens.
  26. */
  27. private static final String COMPLETE_TEST_FILE = "Complete.R";
  28. private final Logger logger = LoggerFactory.getLogger("R frontend test");
  29. private final String[] testFiles = new String[] {"Game.R", COMPLETE_TEST_FILE};
  30. private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "R").toFile();
  31. private Language language;
  32. @BeforeEach
  33. void setup() {
  34. TestErrorConsumer consumer = new TestErrorConsumer();
  35. language = new Language(consumer);
  36. }
  37. @Test
  38. void parseTestFiles() {
  39. for (String fileName : testFiles) {
  40. TokenList tokens = language.parse(testFileLocation, new String[] {fileName});
  41. String output = TokenPrinter.printTokens(tokens, testFileLocation, List.of(fileName));
  42. logger.info(output);
  43. testSourceCoverage(fileName, tokens);
  44. if (fileName.equals(COMPLETE_TEST_FILE))
  45. testTokenCoverage(tokens, fileName);
  46. }
  47. }
  48. /**
  49. * Confirms that the code is covered to a basic extent, i.e. each line of code contains at least one token.
  50. * @param fileName a code sample file name
  51. * @param tokens the TokenList generated from the sample
  52. */
  53. private void testSourceCoverage(String fileName, TokenList tokens) {
  54. File testFile = new File(testFileLocation, fileName);
  55. try {
  56. List<String> lines = Files.readAllLines(testFile.toPath());
  57. String emptyLineExpression = getNoCodeLineExpression();
  58. // All lines that contain code
  59. var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches(emptyLineExpression))
  60. .toArray();
  61. // All lines that contain token
  62. var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray();
  63. if (codeLines.length > tokenLines.length) {
  64. var diffLine = IntStream.range(0, codeLines.length)
  65. .dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst();
  66. diffLine.ifPresent(
  67. lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName)));
  68. }
  69. assertArrayEquals(codeLines, tokenLines);
  70. } catch (IOException exception) {
  71. logger.info("Error while reading test file %s".formatted(fileName), exception);
  72. fail();
  73. }
  74. }
  75. /**
  76. * Confirms that all Token types are 'reachable' with a complete code example.
  77. * @param tokens TokenList which is supposed to contain all types of tokens
  78. * @param fileName The file name of the complete code example
  79. */
  80. private void testTokenCoverage(TokenList tokens, String fileName) {
  81. var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType).sorted().distinct().toArray();
  82. // Exclude SEPARATOR_TOKEN, as it does not occur
  83. var allTokens = IntStream.range(0, RTokenConstants.NUM_DIFF_TOKENS).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray();
  84. if (allTokens.length > foundTokens.length) {
  85. var diffLine = IntStream.range(0, allTokens.length)
  86. .dropWhile(lineIdx -> lineIdx < foundTokens.length && allTokens[lineIdx] == foundTokens[lineIdx]).findFirst();
  87. diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'."
  88. .formatted(new RToken(allTokens[lineIdx], fileName, -1, -1, -1).type2string(), fileName)));
  89. }
  90. assertArrayEquals(allTokens, foundTokens);
  91. }
  92. private static String getNoCodeLineExpression() {
  93. return R_NO_CODE_LINE;
  94. }
  95. }