/vendor/closure-compiler/test/com/google/javascript/jscomp/CompilerTestCase.java
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}