private final void typeCheck(String externs, String js, DiagnosticType... warningKinds) { parseAndTypeCheck(externs, js); JSError[] warnings = compiler.getWarnings(); JSError[] errors = compiler.getErrors(); String errorMessage = "Expected warning of type:\n" + "================================================================\n" + Arrays.toString(warningKinds) + "================================================================\n" + "but found:\n" + "----------------------------------------------------------------\n" + Arrays.toString(errors) + Arrays.toString(warnings) + "\n" + "----------------------------------------------------------------\n"; assertEquals( errorMessage + "Warning count", warningKinds.length, warnings.length + errors.length); for (JSError warning : warnings) { assertTrue( "Wrong warning type\n" + errorMessage, Arrays.asList(warningKinds).contains(warning.getType())); } for (JSError error : errors) { assertTrue( "Wrong warning type\n" + errorMessage, Arrays.asList(warningKinds).contains(error.getType())); } }
/** * Verifies that the compiler pass's JS output matches the expected output and (optionally) that * an expected warning is issued. Or, if an error is expected, this method just verifies that the * error is encountered. * * @param compiler A compiler that has been initialized via {@link Compiler#init} * @param expected Expected output, or null if an error is expected * @param error Expected error, or null if no error is expected * @param warning Expected warning, or null if no warning is expected * @param description The description of the expected warning, or null if no warning is expected * or if the warning's description should not be examined */ private void test( Compiler compiler, List<SourceFile> expected, DiagnosticType error, DiagnosticType warning, String description) { RecentChange recentChange = new RecentChange(); compiler.addChangeHandler(recentChange); Node root = compiler.parseInputs(); assertNotNull("Unexpected parse error(s): " + Joiner.on("\n").join(compiler.getErrors()), root); if (!expectParseWarningsThisTest) { assertEquals( "Unexpected parse warnings(s): " + Joiner.on("\n").join(compiler.getWarnings()), 0, compiler.getWarnings().length); } if (astValidationEnabled) { (new AstValidator(compiler)).validateRoot(root); } Node externsRoot = root.getFirstChild(); Node mainRoot = root.getLastChild(); // Save the tree for later comparison. Node rootClone = root.cloneTree(); Node externsRootClone = rootClone.getFirstChild(); Node mainRootClone = rootClone.getLastChild(); Map<Node, Node> mtoc = NodeUtil.mapMainToClone(mainRoot, mainRootClone); int numRepetitions = getNumRepetitions(); ErrorManager[] errorManagers = new ErrorManager[numRepetitions]; int aggregateWarningCount = 0; List<JSError> aggregateWarnings = Lists.newArrayList(); boolean hasCodeChanged = false; assertFalse("Code should not change before processing", recentChange.hasCodeChanged()); for (int i = 0; i < numRepetitions; ++i) { if (compiler.getErrorCount() == 0) { errorManagers[i] = new BlackHoleErrorManager(compiler); // Only run process closure primitives once, if asked. if (closurePassEnabled && i == 0) { recentChange.reset(); new ProcessClosurePrimitives(compiler, null, CheckLevel.ERROR, false) .process(null, mainRoot); hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); } // Only run the type checking pass once, if asked. // Running it twice can cause unpredictable behavior because duplicate // objects for the same type are created, and the type system // uses reference equality to compare many types. if (!runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } // Only run the normalize pass once, if asked. if (normalizeEnabled && i == 0) { normalizeActualCode(compiler, externsRoot, mainRoot); } if (enableInferConsts && i == 0) { new InferConsts(compiler).process(externsRoot, mainRoot); } if (computeSideEffects && i == 0) { PureFunctionIdentifier.Driver mark = new PureFunctionIdentifier.Driver(compiler, null, false); mark.process(externsRoot, mainRoot); } if (markNoSideEffects && i == 0) { MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler); mark.process(externsRoot, mainRoot); } if (gatherExternPropertiesEnabled && i == 0) { (new GatherExternProperties(compiler)).process(externsRoot, mainRoot); } recentChange.reset(); getProcessor(compiler).process(externsRoot, mainRoot); if (astValidationEnabled) { (new AstValidator(compiler)).validateRoot(root); } if (checkLineNumbers) { (new LineNumberCheck(compiler)).process(externsRoot, mainRoot); } if (runTypeCheckAfterProcessing && typeCheckEnabled && i == 0) { TypeCheck check = createTypeCheck(compiler, typeCheckLevel); check.processForTesting(externsRoot, mainRoot); } hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged(); aggregateWarningCount += errorManagers[i].getWarningCount(); Collections.addAll(aggregateWarnings, compiler.getWarnings()); if (normalizeEnabled) { boolean verifyDeclaredConstants = true; new Normalize.VerifyConstants(compiler, verifyDeclaredConstants) .process(externsRoot, mainRoot); } } } if (error == null) { assertEquals( "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()), 0, compiler.getErrorCount()); // Verify the symbol table. ErrorManager symbolTableErrorManager = new BlackHoleErrorManager(compiler); Node expectedRoot = null; if (expected != null) { expectedRoot = parseExpectedJs(expected); expectedRoot.detachFromParent(); } JSError[] stErrors = symbolTableErrorManager.getErrors(); if (expectedSymbolTableError != null) { assertEquals("There should be one error.", 1, stErrors.length); assertEquals(expectedSymbolTableError, stErrors[0].getType()); } else { assertEquals( "Unexpected symbol table error(s): " + Joiner.on("\n").join(stErrors), 0, stErrors.length); } if (warning == null) { assertEquals( "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings), 0, aggregateWarningCount); } else { assertEquals( "There should be one warning, repeated " + numRepetitions + " time(s). Warnings: " + aggregateWarnings, numRepetitions, aggregateWarningCount); for (int i = 0; i < numRepetitions; ++i) { JSError[] warnings = errorManagers[i].getWarnings(); JSError actual = warnings[0]; assertEquals(warning, actual.getType()); // Make sure that source information is always provided. if (!allowSourcelessWarnings) { assertTrue( "Missing source file name in warning", actual.sourceName != null && !actual.sourceName.isEmpty()); assertTrue("Missing line number in warning", -1 != actual.lineNumber); assertTrue("Missing char number in warning", -1 != actual.getCharno()); } if (description != null) { assertEquals(description, actual.description); } } } // If we ran normalize on the AST, we must also run normalize on the // clone before checking for changes. if (normalizeEnabled) { normalizeActualCode(compiler, externsRootClone, mainRootClone); } boolean codeChange = !mainRootClone.isEquivalentTo(mainRoot); boolean externsChange = !externsRootClone.isEquivalentTo(externsRoot); // Generally, externs should not be changed by the compiler passes. if (externsChange && !allowExternsChanges) { String explanation = externsRootClone.checkTreeEquals(externsRoot); fail( "Unexpected changes to externs" + "\nExpected: " + compiler.toSource(externsRootClone) + "\nResult: " + compiler.toSource(externsRoot) + "\n" + explanation); } if (!codeChange && !externsChange) { assertFalse( "compiler.reportCodeChange() was called " + "even though nothing changed", hasCodeChanged); } else { assertTrue( "compiler.reportCodeChange() should have been called." + "\nOriginal: " + mainRootClone.toStringTree() + "\nNew: " + mainRoot.toStringTree(), hasCodeChanged); } // Check correctness of the changed-scopes-only traversal NodeUtil.verifyScopeChanges(mtoc, mainRoot, false, compiler); if (expected != null) { if (compareAsTree) { String explanation; if (compareJsDoc) { explanation = expectedRoot.checkTreeEqualsIncludingJsDoc(mainRoot); } else { explanation = expectedRoot.checkTreeEquals(mainRoot); } assertNull( "\nExpected: " + compiler.toSource(expectedRoot) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } else if (expected != null) { String[] expectedSources = new String[expected.size()]; for (int i = 0; i < expected.size(); ++i) { try { expectedSources[i] = expected.get(i).getCode(); } catch (IOException e) { throw new RuntimeException("failed to get source code", e); } } assertEquals(Joiner.on("").join(expectedSources), compiler.toSource(mainRoot)); } } // Verify normalization is not invalidated. Node normalizeCheckRootClone = root.cloneTree(); Node normalizeCheckExternsRootClone = normalizeCheckRootClone.getFirstChild(); Node normalizeCheckMainRootClone = normalizeCheckRootClone.getLastChild(); new PrepareAst(compiler).process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone); String explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull( "Node structure normalization invalidated." + "\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); // TODO(johnlenz): enable this for most test cases. // Currently, this invalidates test for while-loops, for-loop // initializers, and other naming. However, a set of code // (Closure primitive rewrites, etc) runs before the Normalize pass, // so this can't be force on everywhere. if (normalizeEnabled) { new Normalize(compiler, true) .process(normalizeCheckExternsRootClone, normalizeCheckMainRootClone); explanation = normalizeCheckMainRootClone.checkTreeEquals(mainRoot); assertNull( "Normalization invalidated." + "\nExpected: " + compiler.toSource(normalizeCheckMainRootClone) + "\nResult: " + compiler.toSource(mainRoot) + "\n" + explanation, explanation); } } else { String errors = ""; for (JSError actualError : compiler.getErrors()) { errors += actualError.description + "\n"; } assertEquals("There should be one error. " + errors, 1, compiler.getErrorCount()); assertEquals(errors, error, compiler.getErrors()[0].getType()); if (warning != null) { String warnings = ""; for (JSError actualError : compiler.getWarnings()) { warnings += actualError.description + "\n"; } assertEquals("There should be one warning. " + warnings, 1, compiler.getWarningCount()); assertEquals(warnings, warning, compiler.getWarnings()[0].getType()); } } }