java/de/jflex/testing/testsuite/JFlexTestRunner.java (192 lines of code) (raw):

/* * Copyright (C) 2018-2019 Google, LLC. * SPDX-License-Identifier: BSD-3-Clause */ package de.jflex.testing.testsuite; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertWithMessage; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.io.Files; import de.jflex.testing.diff.DiffOutputStream; import de.jflex.testing.testsuite.annotations.NoExceptionThrown; import de.jflex.testing.testsuite.annotations.TestSpec; import de.jflex.util.javac.JavacUtils; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.Optional; import jflex.core.OptionUtils; import jflex.generator.LexGenerator; import jflex.logging.Out; import jflex.option.Options; import org.junit.runner.Description; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.InitializationError; public class JFlexTestRunner extends BlockJUnit4ClassRunner { private final Class<?> klass; private final TestSpec spec; private final PrintStream originalSysOut = System.out; private final PrintStream originalSysErr = System.err; public JFlexTestRunner(Class<?> testClass) throws InitializationError { super(testClass); this.klass = testClass; this.spec = checkNotNull( testClass.getAnnotation(TestSpec.class), "A test running with %s must have a @%s", JFlexTestRunner.class.getName(), TestSpec.class.getName()); } @Override public Description getDescription() { return Description.createTestDescription(klass, "JFlex test case"); } @Override public void run(RunNotifier notifier) { String lexerJavaFileName = generateLexer(notifier); if (lexerJavaFileName != null) { buildLexer(notifier, lexerJavaFileName); } // The lexer must be generated before the other tests are executed, as they can try to // compile this generated code. super.run(notifier); } private String generateLexer(RunNotifier notifier) { Description desc = Description.createTestDescription(klass, "Generate Lexer"); notifier.fireTestStarted(desc); Optional<DiffOutputStream> diffSysOut = injectDiffSysOut(); Optional<DiffOutputStream> diffSysErr = injectDiffSysErr(); try { String lexerJavaFileName; if (spec.generatorThrows() == NoExceptionThrown.class) { lexerJavaFileName = invokeJflex(); } else { lexerJavaFileName = null; generateLexerWithExpectedException(); } assertSystemStream(diffSysOut, "System.out"); assertSystemStream(diffSysErr, "System.err"); return lexerJavaFileName; } catch (Throwable th) { notifier.fireTestFailure(new Failure(desc, th)); return null; } finally { if (diffSysOut.isPresent()) { System.setOut(originalSysOut); } if (diffSysErr.isPresent()) { System.setErr(originalSysErr); } notifier.fireTestFinished(desc); } } private static void assertSystemStream(Optional<DiffOutputStream> diffStream, String streamName) { diffStream.ifPresent( s -> assertWithMessage("Content printed to %s", streamName) .that(s.remainingContent()) .isEmpty()); } private String generateLexerWithExpectedException() { try { invokeJflex(); } catch (Throwable e) { assertWithMessage( "@TestCase indicates that the jflex generation must throw a " + spec.generatorThrows().getSimpleName()) .that(e) .isInstanceOf(spec.generatorThrows()); if (spec.generatorThrowableCause() == Void.class) { assertWithMessage("@TestCase indicates that there is no cause for the generator exception") .that(e.getCause()) .isNull(); } else if (spec.generatorThrowableCause() != NoExceptionThrown.class) { assertWithMessage( "@TestCase indicates that cause of the generator exception is %s but it was %s\n", spec.generatorThrowableCause().getSimpleName(), e.getCause()) .that(e.getCause()) .isInstanceOf(spec.generatorThrowableCause()); } // Expected return null; } throw new AssertionError( "@TestCase indicates that the jflex generation throws a " + spec.generatorThrows().getSimpleName() + " but nothing was thrown"); } private Optional<DiffOutputStream> injectDiffSysOut() { if (!Strings.isNullOrEmpty(spec.sysout())) { File sysoutFile = new File(spec.sysout()); try { DiffOutputStream diffSysOut = new DiffOutputStream(Files.newReader(sysoutFile, StandardCharsets.UTF_8)); diffSysOut.setComparisonFailureHandler( failure -> { throw new Error("System.out differs: " + spec.sysout(), failure); }); Out.setOutputStream(diffSysOut); return Optional.of(diffSysOut); } catch (FileNotFoundException e) { throw new AssertionError( "The golden sysout was not found: " + sysoutFile.getAbsolutePath(), e); } } return Optional.empty(); } private Optional<DiffOutputStream> injectDiffSysErr() { if (!Strings.isNullOrEmpty(spec.syserr())) { File syserrFile = new File(spec.syserr()); try { DiffOutputStream diffSysErr = new DiffOutputStream(Files.newReader(syserrFile, StandardCharsets.UTF_8)); diffSysErr.setComparisonFailureHandler( failure -> { throw new Error("System.err differs: " + spec.syserr(), failure); }); System.setErr(new PrintStream(diffSysErr)); return Optional.of(diffSysErr); } catch (FileNotFoundException e) { throw new AssertionError( "The golden syserr was not found: " + syserrFile.getAbsolutePath(), e); } } return Optional.empty(); } @SuppressWarnings("OrphanedFormatString") private String invokeJflex() { if (Options.encoding == null) { OptionUtils.setDefaultOptions(); } Options.jlex = spec.jlexCompat(); Options.dump = spec.dump(); // verbose is default -- verbose_provided simulates the behaviour of an explicit -v on the // command line if (spec.verbose_provided()) { Options.verbose = true; Options.progress = true; OptionUtils.set_unused_warning(true); } // if both verbose_provided() and quiet() are present, we want quiet() to win if (spec.quiet()) { Options.verbose = false; Options.progress = false; OptionUtils.set_unused_warning(false); } OptionUtils.set_unused_warning(spec.warnUnused()); LexGenerator lexGenerator = new LexGenerator(new File(spec.lex())); String lexerJavaFileName = checkNotNull(lexGenerator.generate()); if (spec.minimizedDfaStatesCount() > 0) { assertWithMessage("There should be %d minimized states in the DFA") .that(lexGenerator.minimizedDfaStatesCount()) .isEqualTo(spec.minimizedDfaStatesCount()); } return lexerJavaFileName; } private void buildLexer(RunNotifier notifier, String lexerJavaFileName) { Description desc = Description.createTestDescription(klass, "Compile java code"); notifier.fireTestStarted(desc); try { JavacUtils.compile(ImmutableList.of(new File(lexerJavaFileName))); notifier.fireTestFinished(desc); } catch (Throwable th) { notifier.fireTestFailure(new Failure(desc, th)); } finally { notifier.fireTestFinished(desc); } } }