/*
* Copyright 2012 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.javascript.jscomp.testing.JSErrorSubject.assertError;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.javascript.jscomp.testing.BlackHoleErrorManager;
import com.google.javascript.rhino.Node;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
/**
* Framework for end-to-end test cases.
*
* @author nicksantos@google.com (Nick Santos)
*/
abstract class IntegrationTestCase extends TestCase {
protected static final Joiner LINE_JOINER = Joiner.on('\n');
/** Externs for the test */
protected static final ImmutableList<SourceFile> DEFAULT_EXTERNS =
ImmutableList.of(
SourceFile.fromCode(
"externs",
LINE_JOINER.join(
"var arguments;",
"var undefined;",
"var Math;",
"var isNaN;",
"var Infinity;",
"/** @interface */",
"var Iterator = function() {};",
"/** @interface */",
"var Iterable = function() {};",
"/** @interface @extends {Iterator} @extends {Iterable} */",
"var IteratorIterable = function() {};",
"/** @interface */",
"function IArrayLike() {};",
// TODO(sdh): See if we can remove IIterableResult and Set once polyfills are
// split
"/** @interface */",
"var IIterableResult = function() {};",
"/** @constructor */",
"var Map;",
"/** @constructor */",
"var Set;",
"/** @constructor */",
"function ObjectPropertyDescriptor() {};",
"",
"/** @constructor */ function Window() {}",
"/** @type {string} */ Window.prototype.name;",
"/** @type {string} */ Window.prototype.offsetWidth;",
"/** @type {Window} */ var window;",
"",
"/** @nosideeffects */ function noSideEffects() {}",
"",
"/**",
" * @constructor",
" * @nosideeffects",
" */",
"function Widget() {}",
"/** @modifies {this} */ Widget.prototype.go = function() {};",
"/** @return {string} */ var widgetToken = function() {};",
"",
"function alert(message) {}",
"",
"/**",
" * @constructor",
" * @implements {IArrayLike}",
" * @return {!Array}",
" * @param {...*} var_args",
" */",
"function Array(var_args) {}",
"",
"/**",
" * @constructor",
" * @return {number}",
" * @param {*=} opt_n",
" */",
"function Number(opt_n) {}",
"",
"/**",
" * @constructor",
" * @return {string}",
" * @param {*=} opt_s",
" */",
"function String(opt_s) {}",
"",
"/**",
" * @constructor",
" * @return {boolean}",
" * @param {*=} opt_b",
" */",
"function Boolean(opt_b) {}",
"",
"/**",
" * @constructor",
" * @return {!TypeError}",
" * @param {*=} opt_message",
" * @param {*=} opt_file",
" * @param {*=} opt_line",
" */",
"function TypeError(opt_message, opt_file, opt_line) {}",
"",
"/**",
" * @constructor",
" * @param {*=} opt_value",
" * @return {!Object}",
" */",
"function Object(opt_value) {}",
"Object.seal;",
"Object.defineProperties;",
"/** @type {!Function} */",
"Object.prototype.constructor;",
"",
"/** @typedef {?} */",
"var symbol;",
"",
"/**",
" * @param {string} s",
" * @return {symbol}",
" */",
"function Symbol(s) {}",
"",
"/**",
" * @param {...*} var_args",
" * @constructor",
" */",
"function Function(var_args) {}",
"/** @param {...*} var_args */",
"Function.prototype.call = function (var_args) {};",
"",
"/** @constructor */",
"function Arguments() {}")));
protected List<SourceFile> externs = DEFAULT_EXTERNS;
// The most recently used compiler.
protected Compiler lastCompiler;
protected boolean normalizeResults = false;
protected String inputFileNamePrefix;
protected String inputFileNameSuffix;
@Override
public void setUp() {
externs = DEFAULT_EXTERNS;
lastCompiler = null;
normalizeResults = false;
inputFileNamePrefix = "i";
inputFileNameSuffix = ".js";
}
protected void testSame(CompilerOptions options, String original) {
testSame(options, new String[] { original });
}
protected void testSame(CompilerOptions options, String[] original) {
test(options, original, original);
}
/**
* Asserts that when compiling with the given compiler options,
* {@code original} is transformed into {@code compiled}.
*/
protected void test(CompilerOptions options,
String original, String compiled) {
test(options, new String[] { original }, new String[] { compiled });
}
/**
* Asserts that when compiling with the given compiler options,
* {@code original} is transformed into {@code compiled}.
*/
protected void test(CompilerOptions options,
String[] original, String[] compiled) {
Compiler compiler = compile(options, original);
assertEquals("Expected no warnings or errors\n" +
"Errors: \n" + Joiner.on("\n").join(compiler.getErrors()) + "\n" +
"Warnings: \n" + Joiner.on("\n").join(compiler.getWarnings()),
0, compiler.getErrors().length + compiler.getWarnings().length);
Node root = compiler.getJsRoot();
Node expectedRoot = parseExpectedCode(compiled, options, normalizeResults);
String explanation = expectedRoot.checkTreeEquals(root);
assertNull("\n"
+ "Expected: " + compiler.toSource(expectedRoot) + "\n"
+ "Result: " + compiler.toSource(root) + "\n"
+ explanation,
explanation);
}
/**
* Asserts that when compiling with the given compiler options,
* there is an error or warning.
*/
protected void test(CompilerOptions options,
String original, DiagnosticType warning) {
test(options, new String[] { original }, warning);
}
protected void test(CompilerOptions options,
String original, String compiled, DiagnosticType warning) {
test(options, new String[] { original }, new String[] { compiled },
warning);
}
protected void test(CompilerOptions options,
String[] original, DiagnosticType warning) {
test(options, original, null, warning);
}
/**
* Asserts that when compiling with the given compiler options,
* there is an error or warning.
*/
protected void test(CompilerOptions options,
String[] original, String[] compiled, DiagnosticType warning) {
Compiler compiler = compile(options, original);
checkUnexpectedErrorsOrWarnings(compiler, 1);
assertEquals("Expected exactly one warning or error",
1, compiler.getErrors().length + compiler.getWarnings().length);
if (compiler.getErrors().length > 0) {
assertError(compiler.getErrors()[0]).hasType(warning);
} else {
assertError(compiler.getWarnings()[0]).hasType(warning);
}
if (compiled != null) {
Node root = compiler.getRoot().getLastChild();
Node expectedRoot = parseExpectedCode(compiled, options, normalizeResults);
String explanation = expectedRoot.checkTreeEquals(root);
assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
"\nResult: " + compiler.toSource(root) +
"\n" + explanation, explanation);
}
}
/**
* Asserts that there is at least one parse error.
*/
protected void testParseError(CompilerOptions options, String original) {
testParseError(options, original, null);
}
/**
* Asserts that there is at least one parse error.
*/
protected void testParseError(CompilerOptions options,
String original, String compiled) {
Compiler compiler = compile(options, original);
for (JSError error : compiler.getErrors()) {
if (!error.getType().equals(RhinoErrorReporter.PARSE_ERROR)) {
fail("Found unexpected error type " + error.getType() + ":\n" + error);
}
}
assertEquals("Unexpected warnings: " +
Joiner.on("\n").join(compiler.getWarnings()),
0, compiler.getWarnings().length);
if (compiled != null) {
Node root = compiler.getRoot().getLastChild();
Node expectedRoot = parseExpectedCode(
new String[] {compiled}, options, normalizeResults);
String explanation = expectedRoot.checkTreeEquals(root);
assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
"\nResult: " + compiler.toSource(root) +
"\n" + explanation, explanation);
}
}
/**
* Asserts that when compiling with the given compiler options,
* there is an error or warning.
*/
protected void test(CompilerOptions options,
String[] original, String[] compiled, DiagnosticType[] warnings) {
Compiler compiler = compile(options, original);
checkUnexpectedErrorsOrWarnings(compiler, warnings.length);
if (compiled != null) {
Node root = compiler.getRoot().getLastChild();
Node expectedRoot = parseExpectedCode(compiled, options, normalizeResults);
String explanation = expectedRoot.checkTreeEquals(root);
assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
"\nResult: " + compiler.toSource(root) +
"\n" + explanation, explanation);
}
}
protected void checkUnexpectedErrorsOrWarnings(
Compiler compiler, int expected) {
int actual = compiler.getErrors().length + compiler.getWarnings().length;
if (actual != expected) {
String msg = "";
for (JSError err : compiler.getErrors()) {
msg += "Error:" + err + "\n";
}
for (JSError err : compiler.getWarnings()) {
msg += "Warning:" + err + "\n";
}
assertEquals("Unexpected warnings or errors.\n " + msg,
expected, actual);
}
}
protected Compiler compile(CompilerOptions options, String original) {
return compile(options, new String[] { original });
}
protected Compiler compile(CompilerOptions options, String[] original) {
Compiler compiler = lastCompiler = new Compiler();
BlackHoleErrorManager.silence(compiler);
compiler.compileModules(
externs,
ImmutableList.copyOf(
CompilerTestCase.createModuleChain(
ImmutableList.copyOf(original), inputFileNamePrefix, inputFileNameSuffix)),
options);
return compiler;
}
/**
* Parse the expected code to compare against.
* We want to run this with similar parsing options, but don't
* want to run the commonjs preprocessing passes (so that we can use this
* to test the commonjs code).
*/
protected Node parseExpectedCode(
String[] original, CompilerOptions options, boolean normalize) {
boolean oldProcessCommonJsModules = options.processCommonJSModules;
options.processCommonJSModules = false;
Node expectedRoot = parse(original, options, normalize);
options.processCommonJSModules = oldProcessCommonJsModules;
return expectedRoot;
}
protected Node parse(
String[] original, CompilerOptions options, boolean normalize) {
Compiler compiler = new Compiler();
List<SourceFile> inputs = new ArrayList<>();
for (int i = 0; i < original.length; i++) {
inputs.add(SourceFile.fromCode(inputFileNamePrefix + i + inputFileNameSuffix, original[i]));
}
compiler.init(externs, inputs, options);
checkUnexpectedErrorsOrWarnings(compiler, 0);
Node all = compiler.parseInputs();
checkUnexpectedErrorsOrWarnings(compiler, 0);
Node n = all.getLastChild();
Node externs = all.getFirstChild();
(new CreateSyntheticBlocks(
compiler, "synStart", "synEnd")).process(externs, n);
if (normalize) {
new Normalize(compiler, false)
.process(compiler.getExternsRoot(), compiler.getJsRoot());
}
return n;
}
/** Creates a CompilerOptions object with google coding conventions. */
abstract CompilerOptions createCompilerOptions();
}