RLanguageTest.java 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package de.jplag.rlang;
  2. import static org.junit.jupiter.api.Assertions.assertArrayEquals;
  3. import static org.junit.jupiter.api.Assertions.assertTrue;
  4. import static org.junit.jupiter.api.Assertions.fail;
  5. import java.io.File;
  6. import java.io.IOException;
  7. import java.nio.file.Files;
  8. import java.nio.file.Path;
  9. import java.util.Arrays;
  10. import java.util.List;
  11. import java.util.stream.Collectors;
  12. import java.util.stream.IntStream;
  13. import org.junit.jupiter.api.BeforeEach;
  14. import org.junit.jupiter.api.Test;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import de.jplag.SharedTokenType;
  18. import de.jplag.Token;
  19. import de.jplag.TokenPrinter;
  20. class RLanguageTest {
  21. /**
  22. * Regular expression for lines that contain no code.
  23. */
  24. private static final String R_NO_CODE_LINE = "\\s*(?:#.*)?";
  25. /**
  26. * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens.
  27. */
  28. private static final String COMPLETE_TEST_FILE = "Complete.R";
  29. private final Logger logger = LoggerFactory.getLogger("R language test");
  30. private final String[] testFiles = new String[] {"Game.R", COMPLETE_TEST_FILE};
  31. private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "rlang").toFile();
  32. private Language language;
  33. @BeforeEach
  34. void setup() {
  35. language = new Language();
  36. }
  37. @Test
  38. void parseTestFiles() {
  39. for (String fileName : testFiles) {
  40. List<Token> tokens = language.parse(testFileLocation, new String[] {fileName});
  41. String output = TokenPrinter.printTokens(tokens, testFileLocation);
  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 list of tokens generated from the sample
  52. */
  53. private void testSourceCoverage(String fileName, List<Token> 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)).toArray();
  60. // All lines that contain token
  61. var tokenLines = tokens.stream().mapToInt(Token::getLine).filter(line -> line != Token.NO_VALUE).distinct().toArray();
  62. if (codeLines.length > tokenLines.length) {
  63. var diffLine = IntStream.range(0, codeLines.length)
  64. .dropWhile(lineIndex -> lineIndex < tokenLines.length && codeLines[lineIndex] == tokenLines[lineIndex]).findFirst();
  65. diffLine.ifPresent(
  66. lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName)));
  67. }
  68. assertArrayEquals(codeLines, tokenLines);
  69. } catch (IOException exception) {
  70. logger.info("Error while reading test file %s".formatted(fileName), exception);
  71. fail();
  72. }
  73. }
  74. /**
  75. * Confirms that all Token types are 'reachable' with a complete code example.
  76. * @param tokens list of tokens which is supposed to contain all types of tokens
  77. * @param fileName The file name of the complete code example
  78. */
  79. private void testTokenCoverage(List<Token> tokens, String fileName) {
  80. var annotatedTokens = tokens.stream().map(Token::getType).collect(Collectors.toSet());
  81. assertTrue(annotatedTokens.contains(SharedTokenType.FILE_END));
  82. var annotatedRTokens = annotatedTokens.stream().filter(RTokenType.class::isInstance).collect(Collectors.toSet());
  83. var allRTokens = RTokenType.values();
  84. var missingRTokens = Arrays.stream(allRTokens).filter(token -> !annotatedRTokens.contains(token)).toList();
  85. assertTrue(missingRTokens.isEmpty(), "The following R tokens are missing in the code example '%s':\n".formatted(fileName)
  86. + String.join("\n", missingRTokens.stream().map(RTokenType::getDescription).toList()));
  87. }
  88. private static String getNoCodeLineExpression() {
  89. return R_NO_CODE_LINE;
  90. }
  91. }