PageRenderTime 127ms CodeModel.GetById 26ms app.highlight 84ms RepoModel.GetById 6ms app.codeStats 0ms

/vendor/closure-compiler/test/com/google/javascript/jscomp/CompilerTestCase.java

https://bitbucket.org/simpler/squeeze
Java | 1060 lines | 507 code | 123 blank | 430 comment | 71 complexity | 5a5503fdfcde4e23e94e2ab6d5721f6d MD5 | raw file
Possible License(s): LGPL-2.0, Apache-2.0, GPL-2.0
   1/*
   2 * Copyright 2006 The Closure Compiler Authors.
   3 *
   4 * Licensed under the Apache License, Version 2.0 (the "License");
   5 * you may not use this file except in compliance with the License.
   6 * You may obtain a copy of the License at
   7 *
   8 *     http://www.apache.org/licenses/LICENSE-2.0
   9 *
  10 * Unless required by applicable law or agreed to in writing, software
  11 * distributed under the License is distributed on an "AS IS" BASIS,
  12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 * See the License for the specific language governing permissions and
  14 * limitations under the License.
  15 */
  16
  17package com.google.javascript.jscomp;
  18
  19import com.google.common.base.Joiner;
  20import com.google.common.base.Preconditions;
  21import com.google.common.base.Predicates;
  22import com.google.common.collect.Lists;
  23import com.google.javascript.jscomp.CodeChangeHandler.RecentChange;
  24import com.google.javascript.jscomp.CompilerOptions.LanguageMode;
  25import com.google.javascript.rhino.Node;
  26import com.google.javascript.rhino.testing.BaseJSTypeTestCase;
  27
  28import junit.framework.TestCase;
  29
  30import java.io.IOException;
  31import java.util.List;
  32
  33/**
  34 * <p>Base class for testing JS compiler classes that change
  35 * the node tree of a compiled JS input.</p>
  36 *
  37 * <p>Pulls in shared functionality from different test cases. Also supports
  38 * node tree comparison for input and output (instead of string comparison),
  39 * which makes it easier to write tests b/c you don't have to get the syntax
  40 * exactly correct to the spacing.</p>
  41 *
  42 */
  43public abstract class CompilerTestCase extends TestCase  {
  44
  45  /** Externs for the test */
  46  private final JSSourceFile[] externsInputs;
  47
  48  /** Whether to compare input and output as trees instead of strings */
  49  private final boolean compareAsTree;
  50
  51  /** Whether to parse type info from JSDoc comments */
  52  protected boolean parseTypeInfo;
  53
  54  /** Whether we check warnings without source information. */
  55  private boolean allowSourcelessWarnings = false;
  56
  57  /** True iff type checking pass runs before pass being tested. */
  58  private boolean typeCheckEnabled = false;
  59
  60  /** Error level reported by type checker. */
  61  private CheckLevel typeCheckLevel;
  62
  63  /** Whether the Normalize pass runs before pass being tested. */
  64  private boolean normalizeEnabled = false;
  65
  66  /** Whether the expected js strings should be normalized. */
  67  private boolean normalizeExpected = false;
  68
  69  /** Whether to check that all line number information is preserved. */
  70  private boolean checkLineNumbers = true;
  71
  72  /**
  73   * An expected symbol table error. Only useful for testing the
  74   * symbol table error-handling.
  75   */
  76  private DiagnosticType expectedSymbolTableError = null;
  77
  78  /**
  79   * Whether the MarkNoSideEffectsCalls pass runs before the pass being tested
  80   */
  81  private boolean markNoSideEffects = false;
  82
  83  /** The most recently used Compiler instance. */
  84  private Compiler lastCompiler;
  85
  86  /**
  87   * Whether to acceptES5 source.
  88   */
  89  private boolean acceptES5 = true;
  90
  91  /**
  92   * Whether externs changes should be allowed for this pass.
  93   */
  94  private boolean allowExternsChanges = false;
  95
  96  /**
  97   * Whether the AST should be validated.
  98   */
  99  private boolean astValidationEnabled = true;
 100
 101  private String filename = "testcode";
 102
 103  /**
 104   * Constructs a test.
 105   *
 106   * @param externs Externs JS as a string
 107   * @param compareAsTree True to compare output & expected as a node tree.
 108   *     99% of the time you want to compare as a tree. There are a few
 109   *     special cases where you don't, like if you want to test the code
 110   *     printing of "unnatural" syntax trees. For example,
 111   *
 112   * <pre>
 113   * IF
 114   *   IF
 115   *     STATEMENT
 116   * ELSE
 117   *   STATEMENT
 118   * </pre>
 119   */
 120  protected CompilerTestCase(String externs, boolean compareAsTree) {
 121    this.externsInputs = new JSSourceFile[] {
 122        JSSourceFile.fromCode("externs", externs)
 123    };
 124    this.compareAsTree = compareAsTree;
 125    this.parseTypeInfo = false;
 126  }
 127
 128  /**
 129   * Constructs a test. Uses AST comparison.
 130   * @param externs Externs JS as a string
 131   */
 132  protected CompilerTestCase(String externs) {
 133    this(externs, true);
 134  }
 135
 136  /**
 137   * Constructs a test. Uses AST comparison and no externs.
 138   */
 139  protected CompilerTestCase() {
 140    this("", true);
 141  }
 142
 143  /**
 144   * Gets the compiler pass instance to use for a test.
 145   *
 146   * @param compiler The compiler
 147   * @return The pass to test
 148   */
 149  protected abstract CompilerPass getProcessor(Compiler compiler);
 150
 151
 152  /**
 153   * Gets the compiler options to use for this test. Use getProcessor to
 154   * determine what passes should be run.
 155   */
 156  protected CompilerOptions getOptions() {
 157    return getOptions(new CompilerOptions());
 158  }
 159
 160  /**
 161   * Gets the compiler options to use for this test. Use getProcessor to
 162   * determine what passes should be run.
 163   */
 164  protected CompilerOptions getOptions(CompilerOptions options) {
 165    if (this.acceptES5) {
 166      options.setLanguageIn(LanguageMode.ECMASCRIPT5);
 167    }
 168
 169    // This doesn't affect whether checkSymbols is run--it just affects
 170    // whether variable warnings are filtered.
 171    options.checkSymbols = true;
 172
 173    options.setWarningLevel(
 174        DiagnosticGroups.MISSING_PROPERTIES, CheckLevel.WARNING);
 175    options.setCodingConvention(getCodingConvention());
 176    return options;
 177  }
 178
 179  protected CodingConvention getCodingConvention() {
 180    return new GoogleCodingConvention();
 181  }
 182
 183  public void setFilename(String filename) {
 184    this.filename = filename;
 185  }
 186
 187  /**
 188   * Returns the number of times the pass should be run before results are
 189   * verified.
 190   */
 191  protected int getNumRepetitions() {
 192    // Since most compiler passes should be idempotent, we run each pass twice
 193    // by default.
 194    return 2;
 195  }
 196
 197  /** Expect warnings without source information. */
 198  void allowSourcelessWarnings() {
 199    allowSourcelessWarnings = true;
 200  }
 201
 202  /** The most recently used JSComp instance. */
 203  Compiler getLastCompiler() {
 204    return lastCompiler;
 205  }
 206
 207  /**
 208   * Whether to allow ECMASCRIPT5 source parsing.
 209   */
 210  protected void enableEcmaScript5(boolean acceptES5) {
 211    this.acceptES5 = acceptES5;
 212  }
 213
 214  /**
 215   * Whether to allow externs changes.
 216   */
 217  protected void allowExternsChanges(boolean allowExternsChanges) {
 218    this.allowExternsChanges = allowExternsChanges;
 219  }
 220
 221  /**
 222   * Perform type checking before running the test pass. This will check
 223   * for type errors and annotate nodes with type information.
 224   *
 225   * @param level the level of severity to report for type errors
 226   *
 227   * @see TypeCheck
 228   */
 229  public void enableTypeCheck(CheckLevel level) {
 230    typeCheckEnabled  = true;
 231    typeCheckLevel = level;
 232  }
 233
 234  /**
 235   * Check to make sure that line numbers were preserved.
 236   */
 237  public void enableLineNumberCheck(boolean newVal) {
 238    checkLineNumbers = newVal;
 239  }
 240
 241  /**
 242   * Do not run type checking before running the test pass.
 243   *
 244   * @see TypeCheck
 245   */
 246  void disableTypeCheck() {
 247    typeCheckEnabled  = false;
 248  }
 249
 250  /**
 251   * Perform AST normalization before running the test pass, and anti-normalize
 252   * after running it.
 253   *
 254   * @see Normalize
 255   */
 256  protected void enableNormalize() {
 257    enableNormalize(true);
 258  }
 259
 260  /**
 261   * Perform AST normalization before running the test pass, and anti-normalize
 262   * after running it.
 263   *
 264   * @param normalizeExpected Whether to perform normalization on the
 265   * expected js result.
 266   * @see Normalize
 267   */
 268  protected void enableNormalize(boolean normalizeExpected) {
 269    normalizeEnabled = true;
 270    this.normalizeExpected = normalizeExpected;
 271  }
 272
 273  /**
 274   * Don't perform AST normalization before running the test pass.
 275   * @see Normalize
 276   */
 277  protected void disableNormalize() {
 278    normalizeEnabled = false;
 279  }
 280
 281  /**
 282   * Run the MarkSideEffectCalls pass before running the test pass.
 283   *
 284   * @see MarkNoSideEffectCalls
 285   */
 286  void enableMarkNoSideEffects() {
 287    markNoSideEffects  = true;
 288  }
 289
 290  /**
 291   * Whether to allow Validate the AST after each run of the pass.
 292   */
 293  protected void enableAstValidation(boolean validate) {
 294    astValidationEnabled = validate;
 295  }
 296
 297  /** Returns a newly created TypeCheck. */
 298  private static TypeCheck createTypeCheck(Compiler compiler,
 299      CheckLevel level) {
 300    ReverseAbstractInterpreter rai =
 301        new SemanticReverseAbstractInterpreter(compiler.getCodingConvention(),
 302            compiler.getTypeRegistry());
 303
 304    return new TypeCheck(compiler, rai, compiler.getTypeRegistry(),
 305        level, CheckLevel.OFF);
 306  }
 307
 308  /**
 309   * Verifies that the compiler pass's JS output matches the expected output.
 310   *
 311   * @param js Input
 312   * @param expected Expected JS output
 313   */
 314  public void test(String js, String expected) {
 315    test(js, expected, (DiagnosticType) null);
 316  }
 317
 318  /**
 319   * Verifies that the compiler pass's JS output matches the expected output,
 320   * or that an expected error is encountered.
 321   *
 322   * @param js Input
 323   * @param expected Expected output, or null if an error is expected
 324   * @param error Expected error, or null if no error is expected
 325   */
 326  public void test(String js, String expected, DiagnosticType error) {
 327    test(js, expected, error, null);
 328  }
 329
 330
 331  /**
 332   * Verifies that the compiler pass's JS output matches the expected output,
 333   * or that an expected error is encountered.
 334   *
 335   * @param js Input
 336   * @param expected Expected output, or null if an error is expected
 337   * @param error Expected error, or null if no error is expected
 338   * @param warning Expected warning, or null if no warning is expected
 339   * @param description The content of the error expected
 340   */
 341  public void test(String js, String expected, DiagnosticType error,
 342                   DiagnosticType warning, String description) {
 343    test(externsInputs, js, expected, error, warning, description);
 344  }
 345
 346  /**
 347   * Verifies that the compiler pass's JS output matches the expected output
 348   * and (optionally) that an expected warning is issued. Or, if an error is
 349   * expected, this method just verifies that the error is encountered.
 350   *
 351   * @param js Input
 352   * @param expected Expected output, or null if an error is expected
 353   * @param error Expected error, or null if no error is expected
 354   * @param warning Expected warning, or null if no warning is expected
 355   */
 356  public void test(String js, String expected,
 357                   DiagnosticType error, DiagnosticType warning) {
 358    test(externsInputs, js, expected, error, warning, null);
 359  }
 360
 361  /**
 362   * Verifies that the compiler pass's JS output matches the expected output
 363   * and (optionally) that an expected warning is issued. Or, if an error is
 364   * expected, this method just verifies that the error is encountered.
 365   *
 366   * @param externs Externs input
 367   * @param js Input
 368   * @param expected Expected output, or null if an error is expected
 369   * @param error Expected error, or null if no error is expected
 370   * @param warning Expected warning, or null if no warning is expected
 371   */
 372  public void test(String externs, String js, String expected,
 373                   DiagnosticType error, DiagnosticType warning) {
 374    test(externs, js, expected, error, warning, null);
 375  }
 376
 377  /**
 378   * Verifies that the compiler pass's JS output matches the expected output
 379   * and (optionally) that an expected warning is issued. Or, if an error is
 380   * expected, this method just verifies that the error is encountered.
 381   *
 382   * @param externs Externs input
 383   * @param js Input
 384   * @param expected Expected output, or null if an error is expected
 385   * @param error Expected error, or null if no error is expected
 386   * @param warning Expected warning, or null if no warning is expected
 387   * @param description The description of the expected warning,
 388   *      or null if no warning is expected or if the warning's description
 389   *      should not be examined
 390   */
 391  public void test(String externs, String js, String expected,
 392                   DiagnosticType error, DiagnosticType warning,
 393                   String description) {
 394    JSSourceFile[] externsInputs = new JSSourceFile[]{
 395        JSSourceFile.fromCode("externs", externs)
 396    };
 397    test(externsInputs, js, expected, error, warning, description);
 398  }
 399
 400  /**
 401   * Verifies that the compiler pass's JS output matches the expected output
 402   * and (optionally) that an expected warning is issued. Or, if an error is
 403   * expected, this method just verifies that the error is encountered.
 404   *
 405   * @param externs Externs inputs
 406   * @param js Input
 407   * @param expected Expected output, or null if an error is expected
 408   * @param error Expected error, or null if no error is expected
 409   * @param warning Expected warning, or null if no warning is expected
 410   * @param description The description of the expected warning,
 411   *      or null if no warning is expected or if the warning's description
 412   *      should not be examined
 413   */
 414  public void test(JSSourceFile[] externs, String js, String expected,
 415                   DiagnosticType error,
 416                   DiagnosticType warning, String description) {
 417    Compiler compiler = createCompiler();
 418    lastCompiler = compiler;
 419
 420    CompilerOptions options = getOptions();
 421
 422    if (this.acceptES5) {
 423      options.setLanguageIn(LanguageMode.ECMASCRIPT5);
 424    }
 425    // Note that in this context, turning on the checkTypes option won't
 426    // actually cause the type check to run.
 427    options.checkTypes = parseTypeInfo;
 428    compiler.init(externs, new JSSourceFile[] {
 429        JSSourceFile.fromCode(filename, js) }, options);
 430
 431    BaseJSTypeTestCase.addNativeProperties(compiler.getTypeRegistry());
 432
 433    test(compiler, new String[] { expected }, error, warning, description);
 434  }
 435
 436  /**
 437   * Verifies that the compiler pass's JS output matches the expected output.
 438   *
 439   * @param js Inputs
 440   * @param expected Expected JS output
 441   */
 442  public void test(String[] js, String[] expected) {
 443    test(js, expected, null);
 444  }
 445
 446  /**
 447   * Verifies that the compiler pass's JS output matches the expected output,
 448   * or that an expected error is encountered.
 449   *
 450   * @param js Inputs
 451   * @param expected Expected JS output
 452   * @param error Expected error, or null if no error is expected
 453   */
 454  public void test(String[] js, String[] expected, DiagnosticType error) {
 455    test(js, expected, error, null);
 456  }
 457
 458  /**
 459   * Verifies that the compiler pass's JS output matches the expected output
 460   * and (optionally) that an expected warning is issued. Or, if an error is
 461   * expected, this method just verifies that the error is encountered.
 462   *
 463   * @param js Inputs
 464   * @param expected Expected JS output
 465   * @param error Expected error, or null if no error is expected
 466   * @param warning Expected warning, or null if no warning is expected
 467   */
 468  public void test(String[] js, String[] expected, DiagnosticType error,
 469                   DiagnosticType warning) {
 470    test(js, expected, error, warning, null);
 471  }
 472
 473  /**
 474   * Verifies that the compiler pass's JS output matches the expected output
 475   * and (optionally) that an expected warning is issued. Or, if an error is
 476   * expected, this method just verifies that the error is encountered.
 477   *
 478   * @param js Inputs
 479   * @param expected Expected JS output
 480   * @param error Expected error, or null if no error is expected
 481   * @param warning Expected warning, or null if no warning is expected
 482   * @param description The description of the expected warning,
 483   *      or null if no warning is expected or if the warning's description
 484   *      should not be examined
 485   */
 486  public void test(String[] js, String[] expected, DiagnosticType error,
 487                   DiagnosticType warning, String description) {
 488    Compiler compiler = createCompiler();
 489    lastCompiler = compiler;
 490
 491    JSSourceFile[] inputs = new JSSourceFile[js.length];
 492    for (int i = 0; i < js.length; i++) {
 493      inputs[i] = JSSourceFile.fromCode("input" + i, js[i]);
 494    }
 495    compiler.init(externsInputs, inputs, getOptions());
 496    test(compiler, expected, error, warning, description);
 497  }
 498
 499  /**
 500   * Verifies that the compiler pass's JS output matches the expected output.
 501   *
 502   * @param modules Module inputs
 503   * @param expected Expected JS outputs (one per module)
 504   */
 505  public void test(JSModule[] modules, String[] expected) {
 506    test(modules, expected, null);
 507  }
 508
 509  /**
 510   * Verifies that the compiler pass's JS output matches the expected output,
 511   * or that an expected error is encountered.
 512   *
 513   * @param modules Module inputs
 514   * @param expected Expected JS outputs (one per module)
 515   * @param error Expected error, or null if no error is expected
 516   */
 517  public void test(JSModule[] modules, String[] expected,
 518      DiagnosticType error) {
 519    test(modules, expected, error, null);
 520  }
 521
 522  /**
 523   * Verifies that the compiler pass's JS output matches the expected output
 524   * and (optionally) that an expected warning is issued. Or, if an error is
 525   * expected, this method just verifies that the error is encountered.
 526   *
 527   * @param modules Module inputs
 528   * @param expected Expected JS outputs (one per module)
 529   * @param error Expected error, or null if no error is expected
 530   * @param warning Expected warning, or null if no warning is expected
 531   */
 532  public void test(JSModule[] modules, String[] expected,
 533                   DiagnosticType error, DiagnosticType warning) {
 534    Compiler compiler = createCompiler();
 535    lastCompiler = compiler;
 536
 537    compiler.init(externsInputs, modules, getOptions());
 538    test(compiler, expected, error, warning);
 539  }
 540
 541  /**
 542   * Verifies that the compiler pass's JS output is the same as its input.
 543   *
 544   * @param js Input and output
 545   */
 546  public void testSame(String js) {
 547    test(js, js);
 548  }
 549
 550  /**
 551   * Verifies that the compiler pass's JS output is the same as its input
 552   * and (optionally) that an expected warning is issued.
 553   *
 554   * @param js Input and output
 555   * @param warning Expected warning, or null if no warning is expected
 556   */
 557  public void testSame(String js, DiagnosticType warning) {
 558    test(js, js, null, warning);
 559  }
 560
 561  /**
 562   * Verifies that the compiler pass's JS output is the same as its input
 563   * and (optionally) that an expected warning is issued.
 564   *
 565   * @param js Input and output
 566   * @param diag Expected error or warning, or null if none is expected
 567   * @param error true if diag is an error, false if it is a warning
 568   */
 569  public void testSame(String js, DiagnosticType diag, boolean error) {
 570    if (error) {
 571      test(js, js, diag);
 572    } else {
 573      test(js, js, null, diag);
 574    }
 575  }
 576
 577  /**
 578   * Verifies that the compiler pass's JS output is the same as its input
 579   * and (optionally) that an expected warning is issued.
 580   *
 581   * @param externs Externs input
 582   * @param js Input and output
 583   * @param warning Expected warning, or null if no warning is expected
 584   */
 585  public void testSame(String externs, String js, DiagnosticType warning) {
 586    testSame(externs, js, warning, null);
 587  }
 588
 589  /**
 590   * Verifies that the compiler pass's JS output is the same as its input
 591   * and (optionally) that an expected warning is issued.
 592   *
 593   * @param externs Externs input
 594   * @param js Input and output
 595   * @param diag Expected error or warning, or null if none is expected
 596   * @param error true if diag is an error, false if it is a warning
 597   */
 598  public void testSame(
 599      String externs, String js, DiagnosticType diag, boolean error) {
 600    if (error) {
 601      test(externs, js, js, diag, null);
 602    } else {
 603      test(externs, js, js, null, diag);
 604    }
 605  }
 606
 607  /**
 608   * Verifies that the compiler pass's JS output is the same as its input
 609   * and (optionally) that an expected warning and description is issued.
 610   *
 611   * @param externs Externs input
 612   * @param js Input and output
 613   * @param warning Expected warning, or null if no warning is expected
 614   * @param description The description of the expected warning,
 615   *      or null if no warning is expected or if the warning's description
 616   *      should not be examined
 617   */
 618  public void testSame(String externs, String js, DiagnosticType warning,
 619                       String description) {
 620    JSSourceFile[] externsInputs = new JSSourceFile[]{
 621        JSSourceFile.fromCode("externs", externs)
 622    };
 623    test(externsInputs, js, js, null, warning, description);
 624  }
 625
 626  /**
 627   * Verifies that the compiler pass's JS output is the same as its input.
 628   *
 629   * @param js Inputs and outputs
 630   */
 631  public void testSame(String[] js) {
 632    test(js, js);
 633  }
 634
 635  /**
 636   * Verifies that the compiler pass's JS output is the same as its input,
 637   * and emits the given error.
 638   *
 639   * @param js Inputs and outputs
 640   * @param error Expected error, or null if no error is expected
 641   */
 642  public void testSame(String[] js, DiagnosticType error) {
 643    test(js, js, error);
 644  }
 645
 646  /**
 647   * Verifies that the compiler pass's JS output is the same as its input,
 648   * and emits the given error and warning.
 649   *
 650   * @param js Inputs and outputs
 651   * @param error Expected error, or null if no error is expected
 652   * @param warning Expected warning, or null if no warning is expected
 653   */
 654  public void testSame(
 655      String[] js, DiagnosticType error, DiagnosticType warning) {
 656    test(js, js, error, warning);
 657  }
 658
 659  /**
 660   * Verifies that the compiler pass's JS output is the same as the input.
 661   *
 662   * @param modules Module inputs
 663   */
 664  public void testSame(JSModule[] modules) {
 665    testSame(modules, null);
 666  }
 667
 668  /**
 669   * Verifies that the compiler pass's JS output is the same as the input.
 670   *
 671   * @param modules Module inputs
 672   * @param warning A warning, or null for no expected warning.
 673   */
 674  public void testSame(JSModule[] modules, DiagnosticType warning) {
 675    try {
 676      String[] expected = new String[modules.length];
 677      for (int i = 0; i < modules.length; i++) {
 678        expected[i] = "";
 679        for (CompilerInput input : modules[i].getInputs()) {
 680          expected[i] += input.getSourceFile().getCode();
 681        }
 682      }
 683      test(modules, expected, null, warning);
 684    } catch (IOException e) {
 685      throw new RuntimeException(e);
 686    }
 687  }
 688
 689  /**
 690   * Verifies that the compiler pass's JS output matches the expected output
 691   * and (optionally) that an expected warning is issued. Or, if an error is
 692   * expected, this method just verifies that the error is encountered.
 693   *
 694   * @param compiler A compiler that has been initialized via
 695   *     {@link Compiler#init}
 696   * @param expected Expected output, or null if an error is expected
 697   * @param error Expected error, or null if no error is expected
 698   * @param warning Expected warning, or null if no warning is expected
 699   */
 700  protected void test(Compiler compiler, String[] expected,
 701                      DiagnosticType error, DiagnosticType warning) {
 702    test(compiler, expected, error, warning, null);
 703  }
 704
 705
 706  /**
 707   * Verifies that the compiler pass's JS output matches the expected output
 708   * and (optionally) that an expected warning is issued. Or, if an error is
 709   * expected, this method just verifies that the error is encountered.
 710   *
 711   * @param compiler A compiler that has been initialized via
 712   *     {@link Compiler#init}
 713   * @param expected Expected output, or null if an error is expected
 714   * @param error Expected error, or null if no error is expected
 715   * @param warning Expected warning, or null if no warning is expected
 716   * @param description The description of the expected warning,
 717   *      or null if no warning is expected or if the warning's description
 718   *      should not be examined
 719   */
 720  private void test(Compiler compiler, String[] expected,
 721                    DiagnosticType error, DiagnosticType warning,
 722                    String description) {
 723    RecentChange recentChange = new RecentChange();
 724    compiler.addChangeHandler(recentChange);
 725
 726    Node root = compiler.parseInputs();
 727    assertTrue("Unexpected parse error(s): " +
 728        Joiner.on("\n").join(compiler.getErrors()), root != null);
 729
 730    if (astValidationEnabled) {
 731      (new AstValidator()).validateRoot(root);
 732    }
 733    Node externsRoot = root.getFirstChild();
 734    Node mainRoot = root.getLastChild();
 735
 736    // Save the tree for later comparison.
 737    Node rootClone = root.cloneTree();
 738    Node externsRootClone = rootClone.getFirstChild();
 739    Node mainRootClone = rootClone.getLastChild();
 740
 741    int numRepetitions = getNumRepetitions();
 742    ErrorManager[] errorManagers = new ErrorManager[numRepetitions];
 743    int aggregateWarningCount = 0;
 744    List<JSError> aggregateWarnings = Lists.newArrayList();
 745    boolean hasCodeChanged = false;
 746
 747    assertFalse("Code should not change before processing",
 748        recentChange.hasCodeChanged());
 749
 750    for (int i = 0; i < numRepetitions; ++i) {
 751      if (compiler.getErrorCount() == 0) {
 752        errorManagers[i] = new BlackHoleErrorManager(compiler);
 753
 754        // Only run the type checking pass once, if asked.
 755        // Running it twice can cause unpredictable behavior because duplicate
 756        // objects for the same type are created, and the type system
 757        // uses reference equality to compare many types.
 758        if (typeCheckEnabled && i == 0) {
 759          TypeCheck check = createTypeCheck(compiler, typeCheckLevel);
 760          check.processForTesting(externsRoot, mainRoot);
 761        }
 762
 763        // Only run the normalize pass once, if asked.
 764        if (normalizeEnabled && i == 0) {
 765          normalizeActualCode(compiler, externsRoot, mainRoot);
 766        }
 767
 768        if (markNoSideEffects && i == 0) {
 769          MarkNoSideEffectCalls mark = new MarkNoSideEffectCalls(compiler);
 770          mark.process(externsRoot, mainRoot);
 771        }
 772
 773        recentChange.reset();
 774
 775        getProcessor(compiler).process(externsRoot, mainRoot);
 776        if (astValidationEnabled) {
 777          (new AstValidator()).validateRoot(root);
 778        }
 779        if (checkLineNumbers) {
 780          (new LineNumberCheck(compiler)).process(externsRoot, mainRoot);
 781        }
 782
 783        hasCodeChanged = hasCodeChanged || recentChange.hasCodeChanged();
 784        aggregateWarningCount += errorManagers[i].getWarningCount();
 785        aggregateWarnings.addAll(Lists.newArrayList(compiler.getWarnings()));
 786
 787        if (normalizeEnabled) {
 788          boolean verifyDeclaredConstants = true;
 789          new Normalize.VerifyConstants(compiler, verifyDeclaredConstants)
 790              .process(externsRoot, mainRoot);
 791        }
 792      }
 793    }
 794
 795    if (error == null) {
 796      assertEquals(
 797          "Unexpected error(s): " + Joiner.on("\n").join(compiler.getErrors()),
 798          0, compiler.getErrorCount());
 799
 800      // Verify the symbol table.
 801      ErrorManager symbolTableErrorManager =
 802          new BlackHoleErrorManager(compiler);
 803      Node expectedRoot = parseExpectedJs(expected);
 804      expectedRoot.detachFromParent();
 805
 806      JSError[] stErrors = symbolTableErrorManager.getErrors();
 807      if (expectedSymbolTableError != null) {
 808        assertEquals("There should be one error.", 1, stErrors.length);
 809        assertEquals(expectedSymbolTableError, stErrors[0].getType());
 810      } else {
 811        assertEquals("Unexpected symbol table error(s): " +
 812            Joiner.on("\n").join(stErrors),
 813            0, stErrors.length);
 814      }
 815
 816      if (warning == null) {
 817        assertEquals(
 818            "Unexpected warning(s): " + Joiner.on("\n").join(aggregateWarnings),
 819            0, aggregateWarningCount);
 820      } else {
 821        assertEquals("There should be one warning, repeated " + numRepetitions +
 822            " time(s).", numRepetitions, aggregateWarningCount);
 823        for (int i = 0; i < numRepetitions; ++i) {
 824          JSError[] warnings = errorManagers[i].getWarnings();
 825          JSError actual = warnings[0];
 826          assertEquals(warning, actual.getType());
 827
 828          // Make sure that source information is always provided.
 829          if (!allowSourcelessWarnings) {
 830            assertTrue("Missing source file name in warning",
 831                actual.sourceName != null && !actual.sourceName.isEmpty());
 832            assertTrue("Missing line number in warning",
 833                -1 != actual.lineNumber);
 834            assertTrue("Missing char number in warning",
 835                -1 != actual.getCharno());
 836          }
 837
 838          if (description != null) {
 839            assertEquals(description, actual.description);
 840          }
 841        }
 842      }
 843
 844      if (normalizeEnabled) {
 845        normalizeActualCode(compiler, externsRootClone, mainRootClone);
 846      }
 847
 848      boolean codeChange = !mainRootClone.isEquivalentTo(mainRoot);
 849      boolean externsChange = !externsRootClone.isEquivalentTo(externsRoot);
 850
 851      // Generally, externs should not be change by the compiler passes.
 852      if (externsChange && !allowExternsChanges) {
 853        String explanation = externsRootClone.checkTreeEquals(externsRoot);
 854        fail("Unexpected changes to externs" +
 855            "\nExpected: " + compiler.toSource(externsRootClone) +
 856            "\nResult: " + compiler.toSource(externsRoot) +
 857            "\n" + explanation);
 858      }
 859
 860      if (!codeChange && !externsChange) {
 861        assertFalse(
 862            "compiler.reportCodeChange() was called " +
 863            "even though nothing changed",
 864            hasCodeChanged);
 865      } else {
 866        assertTrue("compiler.reportCodeChange() should have been called",
 867            hasCodeChanged);
 868      }
 869
 870      if (compareAsTree) {
 871        String explanation = expectedRoot.checkTreeEquals(mainRoot);
 872        assertNull("\nExpected: " + compiler.toSource(expectedRoot) +
 873            "\nResult: " + compiler.toSource(mainRoot) +
 874            "\n" + explanation, explanation);
 875      } else if (expected != null) {
 876        assertEquals(
 877            Joiner.on("").join(expected), compiler.toSource(mainRoot));
 878      }
 879
 880      // Verify normalization is not invalidated.
 881      Node normalizeCheckRootClone = root.cloneTree();
 882      Node normalizeCheckExternsRootClone = root.getFirstChild();
 883      Node normalizeCheckMainRootClone = root.getLastChild();
 884      new PrepareAst(compiler).process(
 885          normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
 886      String explanation =
 887          normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
 888      assertNull("Node structure normalization invalidated.\nExpected: " +
 889          compiler.toSource(normalizeCheckMainRootClone) +
 890          "\nResult: " + compiler.toSource(mainRoot) +
 891          "\n" + explanation, explanation);
 892
 893      // TODO(johnlenz): enable this for most test cases.
 894      // Currently, this invalidates test for while-loops, for-loop
 895      // initializers, and other naming.  However, a set of code
 896      // (FoldConstants, etc) runs before the Normalize pass, so this can't be
 897      // force on everywhere.
 898      if (normalizeEnabled) {
 899        new Normalize(compiler, true).process(
 900            normalizeCheckExternsRootClone, normalizeCheckMainRootClone);
 901        explanation =  normalizeCheckMainRootClone.checkTreeEquals(mainRoot);
 902        assertNull("Normalization invalidated.\nExpected: " +
 903            compiler.toSource(normalizeCheckMainRootClone) +
 904            "\nResult: " + compiler.toSource(mainRoot) +
 905            "\n" + explanation, explanation);
 906      }
 907    } else {
 908      String errors = "";
 909      for (JSError actualError : compiler.getErrors()) {
 910        errors += actualError.description + "\n";
 911      }
 912      assertEquals("There should be one error. " + errors,
 913          1, compiler.getErrorCount());
 914      assertEquals(errors, error, compiler.getErrors()[0].getType());
 915
 916      if (warning != null) {
 917        String warnings = "";
 918        for (JSError actualError : compiler.getWarnings()) {
 919          warnings += actualError.description + "\n";
 920        }
 921        assertEquals("There should be one warning. " + warnings,
 922            1, compiler.getWarningCount());
 923        assertEquals(warnings, warning, compiler.getWarnings()[0].getType());
 924      }
 925    }
 926  }
 927
 928  private void normalizeActualCode(
 929      Compiler compiler, Node externsRoot, Node mainRoot) {
 930    Normalize normalize = new Normalize(compiler, false);
 931    normalize.process(externsRoot, mainRoot);
 932  }
 933
 934  /**
 935   * Parses expected js inputs and returns the root of the parse tree.
 936   */
 937  protected Node parseExpectedJs(String[] expected) {
 938    Compiler compiler = createCompiler();
 939    JSSourceFile[] inputs = new JSSourceFile[expected.length];
 940    for (int i = 0; i < expected.length; i++) {
 941      inputs[i] = JSSourceFile.fromCode("expected" + i, expected[i]);
 942    }
 943    compiler.init(externsInputs, inputs, getOptions());
 944    Node root = compiler.parseInputs();
 945    assertTrue("Unexpected parse error(s): " +
 946        Joiner.on("\n").join(compiler.getErrors()), root != null);
 947    Node externsRoot = root.getFirstChild();
 948    Node mainRoot = externsRoot.getNext();
 949    // Only run the normalize pass, if asked.
 950    if (normalizeEnabled && normalizeExpected && !compiler.hasErrors()) {
 951      Normalize normalize = new Normalize(compiler, false);
 952      normalize.process(externsRoot, mainRoot);
 953    }
 954    return mainRoot;
 955  }
 956
 957  protected Node parseExpectedJs(String expected) {
 958    return parseExpectedJs(new String[] {expected});
 959  }
 960
 961  /**
 962   * Generates a list of modules from a list of inputs, such that each module
 963   * depends on the module before it.
 964   */
 965  static JSModule[] createModuleChain(String... inputs) {
 966    JSModule[] modules = createModules(inputs);
 967    for (int i = 1; i < modules.length; i++) {
 968      modules[i].addDependency(modules[i - 1]);
 969    }
 970    return modules;
 971  }
 972
 973  /**
 974   * Generates a list of modules from a list of inputs, such that each module
 975   * depends on the first module.
 976   */
 977  static JSModule[] createModuleStar(String... inputs) {
 978    JSModule[] modules = createModules(inputs);
 979    for (int i = 1; i < modules.length; i++) {
 980      modules[i].addDependency(modules[0]);
 981    }
 982    return modules;
 983  }
 984
 985  /**
 986   * Generates a list of modules from a list of inputs, such that modules
 987   * form a bush formation. In a bush formation, module 2 depends
 988   * on module 1, and all other modules depend on module 2.
 989   */
 990  static JSModule[] createModuleBush(String ... inputs) {
 991    Preconditions.checkState(inputs.length > 2);
 992    JSModule[] modules = createModules(inputs);
 993    for (int i = 1; i < modules.length; i++) {
 994      modules[i].addDependency(modules[i == 1 ? 0 : 1]);
 995    }
 996    return modules;
 997  }
 998
 999  /**
1000   * Generates a list of modules from a list of inputs, such that modules
1001   * form a tree formation. In a tree formation, module N depends on
1002   * module `floor(N/2)`, So the modules form a balanced binary tree.
1003   */
1004  static JSModule[] createModuleTree(String ... inputs) {
1005    JSModule[] modules = createModules(inputs);
1006    for (int i = 1; i < modules.length; i++) {
1007      modules[i].addDependency(modules[(i - 1) / 2]);
1008    }
1009    return modules;
1010  }
1011
1012  /**
1013   * Generates a list of modules from a list of inputs. Does not generate any
1014   * dependencies between the modules.
1015   */
1016  static JSModule[] createModules(String... inputs) {
1017    JSModule[] modules = new JSModule[inputs.length];
1018    for (int i = 0; i < inputs.length; i++) {
1019      JSModule module = modules[i] = new JSModule("m" + i);
1020      module.add(JSSourceFile.fromCode("i" + i, inputs[i]));
1021    }
1022    return modules;
1023  }
1024
1025  private static class BlackHoleErrorManager extends BasicErrorManager {
1026    private BlackHoleErrorManager(Compiler compiler) {
1027      compiler.setErrorManager(this);
1028    }
1029
1030    @Override
1031    public void println(CheckLevel level, JSError error) {}
1032
1033    @Override
1034    public void printSummary() {}
1035  }
1036
1037  Compiler createCompiler() {
1038    Compiler compiler = new Compiler();
1039    return compiler;
1040  }
1041
1042  protected void setExpectedSymbolTableError(DiagnosticType type) {
1043    this.expectedSymbolTableError = type;
1044  }
1045
1046  /** Finds the first matching qualified name node in post-traversal order. */
1047  protected final Node findQualifiedNameNode(final String name, Node root) {
1048    final List<Node> matches = Lists.newArrayList();
1049    NodeUtil.visitPostOrder(root,
1050        new NodeUtil.Visitor() {
1051          @Override public void visit(Node n) {
1052            if (name.equals(n.getQualifiedName())) {
1053              matches.add(n);
1054            }
1055          }
1056        },
1057        Predicates.<Node>alwaysTrue());
1058    return matches.get(0);
1059  }
1060}