1 /*******************************************************************************
2  * Copyright (c) 2017 GK Software AG and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Stephan Herrmann - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.core.tests.compiler.regression;
15 
16 import java.io.BufferedReader;
17 import java.io.File;
18 import java.io.FileNotFoundException;
19 import java.io.FileOutputStream;
20 import java.io.IOException;
21 import java.io.PrintWriter;
22 import java.io.StringReader;
23 import java.util.ArrayList;
24 
25 import org.eclipse.jdt.core.JavaCore;
26 import org.eclipse.jdt.core.compiler.CompilationProgress;
27 import org.eclipse.jdt.core.compiler.batch.BatchCompiler;
28 import org.eclipse.jdt.core.tests.util.Util;
29 import org.eclipse.jdt.internal.compiler.batch.ClasspathLocation;
30 import org.eclipse.jdt.internal.compiler.batch.FileSystem;
31 import org.eclipse.jdt.internal.compiler.batch.Main;
32 
33 public abstract class AbstractBatchCompilerTest extends AbstractRegressionTest {
34 
35 	protected static abstract class Matcher {
match(String effective)36 		abstract boolean match(String effective);
expected()37 		abstract String expected(); // for use in JUnit comparison framework
38 	}
39 
40 	/**
41 	 * Abstract normalizer for output comparison. This class merely embodies a
42 	 * chain of responsibility, plus the signature of the method of interest
43 	 * here, that is {@link #normalized(String) normalized}.
44 	 */
45 	protected static abstract class Normalizer {
46 		private Normalizer nextInChain;
Normalizer(Normalizer nextInChain)47 		Normalizer(Normalizer nextInChain) {
48 			this.nextInChain = nextInChain;
49 		}
normalized(String originalValue)50 		String normalized(String originalValue) {
51 			String result;
52 			if (this.nextInChain == null)
53 				result = Util.convertToIndependantLineDelimiter(originalValue);
54 			else
55 				result = this.nextInChain.normalized(originalValue);
56 			return result;
57 		}
58 	}
59 
60 	/**
61 	 * This normalizer replaces occurrences of a given string with a given
62 	 * placeholder.
63 	 */
64 	protected static class StringNormalizer extends Normalizer {
65 		private String match;
66 		private int matchLength;
67 		private String placeholder;
StringNormalizer(Normalizer nextInChain, String match, String placeholder)68 		StringNormalizer(Normalizer nextInChain, String match, String placeholder) {
69 			super(nextInChain);
70 			this.match = match;
71 			this.matchLength = match.length();
72 			this.placeholder = placeholder;
73 		}
74 		@Override
normalized(String originalValue)75 		String normalized(String originalValue) {
76 			String result;
77 			StringBuffer normalizedValueBuffer = new StringBuffer(originalValue);
78 			int nextOccurrenceIndex;
79 			while ((nextOccurrenceIndex = normalizedValueBuffer.indexOf(this.match)) != -1)
80 				normalizedValueBuffer.replace(nextOccurrenceIndex,
81 						nextOccurrenceIndex + this.matchLength, this.placeholder);
82 			result = super.normalized(normalizedValueBuffer.toString());
83 			return result;
84 		}
85 	}
86 
87 	protected static class TestCompilationProgress extends CompilationProgress {
88 		boolean isCanceled = false;
89 		int workedSoFar = 0;
90 		StringBuffer buffer = new StringBuffer();
begin(int remainingWork)91 		public void begin(int remainingWork) {
92 			this.buffer.append("----------\n[worked: 0 - remaining: ").append(remainingWork).append("]\n");
93 		}
done()94 		public void done() {
95 			this.buffer.append("----------\n");
96 		}
isCanceled()97 		public boolean isCanceled() {
98 			return this.isCanceled;
99 		}
setTaskName(String name)100 		public void setTaskName(String name) {
101 			this.buffer.append(name).append('\n');
102 		}
toString()103 		public String toString() {
104 			return this.buffer.toString();
105 		}
worked(int workIncrement, int remainingWork)106 		public void worked(int workIncrement, int remainingWork) {
107 			this.workedSoFar += workIncrement;
108 			this.buffer.append("[worked: ").append(this.workedSoFar).append(" - remaining: ").append(remainingWork).append("]\n");
109 		}
110 	}
111 
112 	public static final String OUTPUT_DIR_PLACEHOLDER = "---OUTPUT_DIR_PLACEHOLDER---";
113 	public static final String LIB_DIR_PLACEHOLDER = "---LIB_DIR_PLACEHOLDER---";
114 
115 	/**
116 	 * Normalizer instance that replaces occurrences of OUTPUT_DIR with
117 	 * OUTPUT_DIR_PLACEHOLDER and changes file separator to / if the
118 	 * platform file separator is different from /.
119 	 */
120 	protected static Normalizer outputDirNormalizer;
121 	static {
122 		if (File.separatorChar == '/') {
123 			outputDirNormalizer =
124 				new StringNormalizer(
125 					new StringNormalizer(
126 						null, OUTPUT_DIR, OUTPUT_DIR_PLACEHOLDER),
127 					LIB_DIR, LIB_DIR_PLACEHOLDER);
128 		}
129 		else {
130 			outputDirNormalizer =
131 				new StringNormalizer(
132 					new StringNormalizer(
133 						new StringNormalizer(
134 							null, File.separator, "/"),
135 						OUTPUT_DIR, OUTPUT_DIR_PLACEHOLDER),
136 					LIB_DIR, LIB_DIR_PLACEHOLDER);
137 		}
138 	}
139 
AbstractBatchCompilerTest(String name)140 	public AbstractBatchCompilerTest(String name) {
141 		super(name);
142 	}
143 
144 	protected static final String JRE_HOME_DIR = Util.getJREDirectory();
145 	protected static final Main MAIN = new Main(null/*outWriter*/, null/*errWriter*/, false/*systemExit*/, null/*options*/, null/*progress*/);
146 
147 	private static boolean CASCADED_JARS_CREATED;
148 	@Override
setUp()149 	protected void setUp() throws Exception {
150 		super.setUp();
151 		CASCADED_JARS_CREATED = false; // initialization needed for each subclass individually
152 	}
153 
createCascadedJars()154 	protected void createCascadedJars() {
155 		if (!CASCADED_JARS_CREATED) {
156 			File libDir = new File(LIB_DIR);
157 			Util.delete(libDir); // make sure we recycle the libs
158 	 		libDir.mkdirs();
159 			try {
160 				Util.createJar(
161 					new String[] {
162 						"p/A.java",
163 						"package p;\n" +
164 						"public class A {\n" +
165 						"}",
166 					},
167 					new String[] {
168 						"META-INF/MANIFEST.MF",
169 						"Manifest-Version: 1.0\n" +
170 						"Created-By: Eclipse JDT Test Harness\n" +
171 						"Class-Path: lib2.jar\n",
172 						"p/S1.java",
173 						"package p;\n" +
174 						"public class S1 {\n" +
175 						"}",
176 					},
177 					LIB_DIR + "/lib1.jar",
178 					JavaCore.VERSION_1_4);
179 				Util.createJar(
180 					new String[] {
181 						"p/B.java",
182 						"package p;\n" +
183 						"public class B {\n" +
184 						"}",
185 						"p/R.java",
186 						"package p;\n" +
187 						"public class R {\n" +
188 						"  public static final int R2 = 2;\n" +
189 						"}",
190 					},
191 					new String[] {
192 						"p/S2.java",
193 						"package p;\n" +
194 						"public class S2 {\n" +
195 						"}",
196 					},
197 					LIB_DIR + "/lib2.jar",
198 					JavaCore.VERSION_1_4);
199 				Util.createJar(
200 					new String[] {
201 						"p/C.java",
202 						"package p;\n" +
203 						"public class C {\n" +
204 						"}",
205 						"p/R.java",
206 						"package p;\n" +
207 						"public class R {\n" +
208 						"  public static final int R3 = 3;\n" +
209 						"}",
210 					},
211 					new String[] {
212 						"META-INF/MANIFEST.MF",
213 						"Manifest-Version: 1.0\n" +
214 						"Created-By: Eclipse JDT Test Harness\n" +
215 						"Class-Path: lib4.jar\n",
216 					},
217 					LIB_DIR + "/lib3.jar",
218 					JavaCore.VERSION_1_4);
219 				Util.createJar(
220 					new String[] {
221 						"p/D.java",
222 						"package p;\n" +
223 						"public class D {\n" +
224 						"}",
225 					},
226 					new String[] {
227 						"META-INF/MANIFEST.MF",
228 						"Manifest-Version: 1.0\n" +
229 						"Created-By: Eclipse JDT Test Harness\n" +
230 						"Class-Path: lib1.jar lib3.jar\n",
231 					},
232 					LIB_DIR + "/lib4.jar",
233 					JavaCore.VERSION_1_4);
234 				Util.createJar(
235 					new String[] {
236 						"p/C.java",
237 						"package p;\n" +
238 						"public class C {\n" +
239 						"}",
240 						"p/R.java",
241 						"package p;\n" +
242 						"public class R {\n" +
243 						"  public static final int R3 = 3;\n" +
244 						"}",
245 					},
246 					new String[] {
247 						"META-INF/MANIFEST.MF",
248 						"Manifest-Version: 1.0\n" +
249 						"Created-By: Eclipse JDT Test Harness\n" +
250 						"Class-Path: s/lib6.jar\n",
251 					},
252 					LIB_DIR + "/lib5.jar",
253 					JavaCore.VERSION_1_4);
254 				new File(LIB_DIR + "/s").mkdir();
255 				Util.createJar(
256 					new String[] {
257 						"p/D.java",
258 						"package p;\n" +
259 						"public class D {\n" +
260 						"}",
261 					},
262 					new String[] {
263 						"META-INF/MANIFEST.MF",
264 						"Manifest-Version: 1.0\n" +
265 						"Created-By: Eclipse JDT Test Harness\n" +
266 						"Class-Path: ../lib7.jar\n",
267 					},
268 					LIB_DIR + "/s/lib6.jar",
269 					JavaCore.VERSION_1_4);
270 				Util.createJar(
271 					new String[] {
272 						"p/A.java",
273 						"package p;\n" +
274 						"public class A {\n" +
275 						"}",
276 					},
277 					new String[] {
278 						"META-INF/MANIFEST.MF",
279 						"Manifest-Version: 1.0\n" +
280 						"Created-By: Eclipse JDT Test Harness\n" +
281 						"Class-Path: lib2.jar\n",
282 					},
283 					LIB_DIR + "/lib7.jar",
284 					JavaCore.VERSION_1_4);
285 				Util.createJar(
286 					new String[] {
287 						"p/F.java",
288 						"package p;\n" +
289 						"public class F {\n" +
290 						"}",
291 					},
292 					new String[] {
293 						"META-INF/MANIFEST.MF",
294 						"Manifest-Version: 1.0\n" +
295 						"Created-By: Eclipse JDT Test Harness\n" +
296 						"Class-Path: " + LIB_DIR + "/lib3.jar lib1.jar\n",
297 					},
298 					LIB_DIR + "/lib8.jar",
299 					JavaCore.VERSION_1_4);
300 				Util.createJar(
301 					new String[] {
302 						"p/G.java",
303 						"package p;\n" +
304 						"public class G {\n" +
305 						"}",
306 					},
307 					new String[] {
308 						"META-INF/MANIFEST.MF",
309 						"Manifest-Version: 1.0\n" +
310 						"Created-By: Eclipse JDT Test Harness\n" +
311 						"Class-Path: lib1.jar\n" +
312 						"Class-Path: lib3.jar\n",
313 					},
314 					LIB_DIR + "/lib9.jar",
315 					JavaCore.VERSION_1_4);
316 				Util.createJar(
317 					new String[] {
318 						"p/A.java",
319 						"package p;\n" +
320 						"public class A {\n" +
321 						"}",
322 					},
323 					// spoiled jar: MANIFEST.MF is a directory
324 					new String[] {
325 						"META-INF/MANIFEST.MF/MANIFEST.MF",
326 						"Manifest-Version: 1.0\n" +
327 						"Created-By: Eclipse JDT Test Harness\n" +
328 						"Class-Path: lib2.jar\n",
329 					},
330 					LIB_DIR + "/lib10.jar",
331 					JavaCore.VERSION_1_4);
332 				Util.createJar(
333 					new String[] {
334 						"p/A.java",
335 						"package p;\n" +
336 						"public class A {\n" +
337 						"}",
338 					},
339 					new String[] {
340 						"META-INF/MANIFEST.MF",
341 						"Manifest-Version: 1.0\n" +
342 						"Created-By: Eclipse JDT Test Harness\n" +
343 						"Class-Path:\n",
344 					},
345 					LIB_DIR + "/lib11.jar",
346 					JavaCore.VERSION_1_4);
347 				Util.createJar(
348 					null,
349 					new String[] {
350 						"META-INF/MANIFEST.MF",
351 						"Manifest-Version: 1.0\n" +
352 						"Created-By: Eclipse JDT Test Harness\n" +
353 						"Class-Path:lib1.jar\n", // missing space
354 					},
355 					LIB_DIR + "/lib12.jar",
356 					JavaCore.VERSION_1_4);
357 				Util.createJar(
358 					null,
359 					new String[] {
360 						"META-INF/MANIFEST.MF",
361 						"Manifest-Version: 1.0\n" +
362 						"Created-By: Eclipse JDT Test Harness\n" +
363 						"Class-Path:lib1.jar lib1.jar\n", // missing space
364 					},
365 					LIB_DIR + "/lib13.jar",
366 					JavaCore.VERSION_1_4);
367 				Util.createJar(
368 					null,
369 					new String[] {
370 						"META-INF/MANIFEST.MF",
371 						"Manifest-Version: 1.0\n" +
372 						"Created-By: Eclipse JDT Test Harness\n" +
373 						" Class-Path: lib1.jar\n", // extra space at line start
374 					},
375 					LIB_DIR + "/lib14.jar",
376 					JavaCore.VERSION_1_4);
377 				Util.createJar(
378 					null,
379 					new String[] {
380 						"META-INF/MANIFEST.MF",
381 						"Manifest-Version: 1.0\n" +
382 						"Created-By: Eclipse JDT Test Harness\n" +
383 						"Class-Path: lib1.jar", // missing newline at end
384 					},
385 					LIB_DIR + "/lib15.jar",
386 					JavaCore.VERSION_1_4);
387 				Util.createJar(
388 					new String[] {
389 						"p/A.java",
390 						"package p;\n" +
391 						"public class A {\n" +
392 						"}",
393 					},
394 					new String[] {
395 						"META-INF/MANIFEST.MF",
396 						"Manifest-Version: 1.0\n" +
397 						"Created-By: Eclipse JDT Test Harness\n" +
398 						"Class-Path: \n" +
399 						" lib2.jar\n",
400 						"p/S1.java",
401 						"package p;\n" +
402 						"public class S1 {\n" +
403 						"}",
404 					},
405 					LIB_DIR + "/lib16.jar",
406 					JavaCore.VERSION_1_4);
407 				new File(LIB_DIR + "/dir").mkdir();
408 				Util.createJar(
409 					new String[] {
410 						"p/A.java",
411 						"package p;\n" +
412 						"public class A {\n" +
413 						"}",
414 					},
415 					new String[] {
416 						"META-INF/MANIFEST.MF",
417 						"Manifest-Version: 1.0\n" +
418 						"Created-By: Eclipse JDT Test Harness\n" +
419 						"Class-Path: ../lib2.jar\n",
420 					},
421 					LIB_DIR + "/dir/lib17.jar",
422 					JavaCore.VERSION_1_4);
423 				CASCADED_JARS_CREATED = true;
424 			} catch (IOException e) {
425 				// ignore
426 			}
427 		}
428 	}
429 
getLibraryClassesAsQuotedString()430 	protected String getLibraryClassesAsQuotedString() {
431 		String[] paths = Util.getJavaClassLibs();
432 		StringBuffer buffer = new StringBuffer();
433 		buffer.append('"');
434 		for (int i = 0, max = paths.length; i < max; i++) {
435 			if (i != 0) {
436 				buffer.append(File.pathSeparatorChar);
437 			}
438 			buffer.append(paths[i]);
439 		}
440 		buffer.append('"');
441 		return String.valueOf(buffer);
442 	}
443 
getJCEJarAsQuotedString()444 	protected String getJCEJarAsQuotedString() {
445 		if (Util.isMacOS()) {
446 			return "\"" + JRE_HOME_DIR + "/../Classes/jce.jar\"";
447 		}
448 		return "\"" + JRE_HOME_DIR + "/lib/jce.jar\"";
449 	}
450 
getExtDirectory()451 	protected String getExtDirectory() {
452 		return JRE_HOME_DIR + "/lib/ext";
453 	}
454 
455 	/**
456 	 * Run a compilation test that is expected to complete successfully and
457 	 * compare the outputs to expected ones.
458 	 *
459 	 * @param testFiles
460 	 *            the source files, given as a suite of file name, file content;
461 	 *            file names are relative to the output directory
462 	 * @param commandLine
463 	 *            the command line to pass to
464 	 *            {@link BatchCompiler#compile(String, PrintWriter, PrintWriter, org.eclipse.jdt.core.compiler.CompilationProgress) BatchCompiler#compile}
465 	 * @param expectedSuccessOutOutputString
466 	 *            the expected contents of the standard output stream; pass null
467 	 *            to bypass the comparison
468 	 * @param expectedSuccessErrOutputString
469 	 *            the expected contents of the standard error output stream;
470 	 *            pass null to bypass the comparison
471 	 * @param shouldFlushOutputDirectory
472 	 *            pass true to get the output directory flushed before the test
473 	 *            runs
474 	 */
runConformTest(String[] testFiles, String commandLine, String expectedSuccessOutOutputString, String expectedSuccessErrOutputString, boolean shouldFlushOutputDirectory)475 	protected void runConformTest(String[] testFiles, String commandLine, String expectedSuccessOutOutputString, String expectedSuccessErrOutputString, boolean shouldFlushOutputDirectory) {
476 		runTest(true, testFiles, commandLine, expectedSuccessOutOutputString,
477 				expectedSuccessErrOutputString, shouldFlushOutputDirectory, null/*progress*/);
478 	}
479 
480 	/**
481 	 * Run a compilation test that is expected to fail and compare the outputs
482 	 * to expected ones.
483 	 *
484 	 * @param testFiles
485 	 *            the source files, given as a suite of file name, file content;
486 	 *            file names are relative to the output directory
487 	 * @param commandLine
488 	 *            the command line to pass to
489 	 *            {@link BatchCompiler#compile(String, PrintWriter, PrintWriter, org.eclipse.jdt.core.compiler.CompilationProgress) BatchCompiler#compile}
490 	 * @param expectedFailureOutOutputString
491 	 *            the expected contents of the standard output stream; pass null
492 	 *            to bypass the comparison
493 	 * @param expectedFailureErrOutputString
494 	 *            the expected contents of the standard error output stream;
495 	 *            pass null to bypass the comparison
496 	 * @param shouldFlushOutputDirectory
497 	 *            pass true to get the output directory flushed before the test
498 	 *            runs
499 	 */
runNegativeTest(String[] testFiles, String commandLine, String expectedFailureOutOutputString, String expectedFailureErrOutputString, boolean shouldFlushOutputDirectory)500 	protected void runNegativeTest(String[] testFiles, String commandLine, String expectedFailureOutOutputString, String expectedFailureErrOutputString, boolean shouldFlushOutputDirectory) {
501 		runTest(false, testFiles, commandLine, expectedFailureOutOutputString,
502 				expectedFailureErrOutputString, shouldFlushOutputDirectory, null/*progress*/);
503 	}
504 
runProgressTest(String[] testFiles, String commandLine, String expectedOutOutputString, String expectedErrOutputString, String expectedProgress)505 	protected void runProgressTest(String[] testFiles, String commandLine, String expectedOutOutputString, String expectedErrOutputString, String expectedProgress) {
506 		runTest(true/*shouldCompileOK*/, testFiles, commandLine, expectedOutOutputString, expectedErrOutputString, true/*shouldFlushOutputDirectory*/, new TestCompilationProgress());
507 	}
508 
runProgressTest(boolean shouldCompileOK, String[] testFiles, String commandLine, String expectedOutOutputString, String expectedErrOutputString, TestCompilationProgress progress, String expectedProgress)509 	protected void runProgressTest(boolean shouldCompileOK, String[] testFiles, String commandLine, String expectedOutOutputString, String expectedErrOutputString, TestCompilationProgress progress, String expectedProgress) {
510 		runTest(shouldCompileOK, testFiles, commandLine, expectedOutOutputString, expectedErrOutputString, true/*shouldFlushOutputDirectory*/, progress);
511 		String actualProgress = progress.toString();
512 		if (!semiNormalizedComparison(expectedProgress, actualProgress, outputDirNormalizer)) {
513 			System.out.println(Util.displayString(outputDirNormalizer.normalized(actualProgress), 2));
514 			assertEquals(
515 				"Unexpected progress",
516 				expectedProgress,
517 				actualProgress);
518 		}
519 	}
520 
521 	/**
522 	 * Worker method for runConformTest and runNegativeTest.
523 	 *
524 	 * @param shouldCompileOK
525 	 *            set to true if the compiler should compile the given sources
526 	 *            without errors
527 	 * @param testFiles
528 	 *            the source files, given as a suite of file name, file content;
529 	 *            file names are relative to the output directory
530 	 * @param extraArguments
531 	 *            the command line to pass to {@link Main#compile(String[])
532 	 *            Main#compile} or other arguments to pass to {@link
533 	 *            #invokeCompiler(PrintWriter, PrintWriter, Object,
534 	 *            BatchCompilerTest.TestCompilationProgress)} (for use
535 	 *            by extending test classes)
536 	 * @param expectedOutOutputString
537 	 *            the expected contents of the standard output stream; pass null
538 	 *            to bypass the comparison
539 	 * @param expectedErrOutputString
540 	 *            the expected contents of the standard error output stream;
541 	 *            pass null to bypass the comparison
542 	 * @param shouldFlushOutputDirectory
543 	 *            pass true to get the output directory flushed before the test
544 	 *            runs
545 	 */
runTest(boolean shouldCompileOK, String[] testFiles, Object extraArguments, String expectedOutOutputString, String expectedErrOutputString, boolean shouldFlushOutputDirectory, TestCompilationProgress progress)546 	protected void runTest(boolean shouldCompileOK, String[] testFiles, Object extraArguments, String expectedOutOutputString, String expectedErrOutputString, boolean shouldFlushOutputDirectory, TestCompilationProgress progress) {
547 		File outputDirectory = new File(OUTPUT_DIR);
548 		if (shouldFlushOutputDirectory)
549 			Util.flushDirectoryContent(outputDirectory);
550 		try {
551 			if (!outputDirectory.isDirectory()) {
552 				outputDirectory.mkdirs();
553 			}
554 			if (testFiles != null) {
555 				PrintWriter sourceFileWriter;
556 				for (int i = 0; i < testFiles.length; i += 2) {
557 					String fileName = OUTPUT_DIR + File.separator + testFiles[i];
558 					File file = new File(fileName), innerOutputDirectory = file
559 							.getParentFile();
560 					if (!innerOutputDirectory.isDirectory()) {
561 						innerOutputDirectory.mkdirs();
562 					}
563 					sourceFileWriter = new PrintWriter(new FileOutputStream(file));
564 					try {
565 						sourceFileWriter.write(testFiles[i + 1]);
566 					} finally {
567 						sourceFileWriter.close();
568 					}
569 				}
570 			}
571 		} catch (FileNotFoundException e) {
572 			e.printStackTrace();
573 			throw new RuntimeException(e);
574 		}
575 		String printerWritersNameRoot = OUTPUT_DIR + File.separator + testName();
576 		String outFileName = printerWritersNameRoot + "out.txt",
577 			   errFileName = printerWritersNameRoot + "err.txt";
578 		PrintWriter out = null;
579 		PrintWriter err = null;
580 		boolean compileOK;
581 		try {
582 			try {
583 				out = new PrintWriter(new FileOutputStream(outFileName));
584 				err = new PrintWriter(new FileOutputStream(errFileName));
585 			} catch (FileNotFoundException e) {
586 				System.out.println(getClass().getName() + '#' + getName());
587 				e.printStackTrace();
588 				throw new RuntimeException(e);
589 			}
590 			compileOK = invokeCompiler(out, err, extraArguments, progress);
591 		} finally {
592 			if (out != null)
593 				out.close();
594 			if (err != null)
595 				err.close();
596 		}
597 		String outOutputString = Util.fileContent(outFileName),
598 		       errOutputString = Util.fileContent(errFileName);
599 		boolean compareOK = false, outCompareOK = false, errCompareOK = false;
600 		if (compileOK == shouldCompileOK) {
601 			compareOK =
602 				(outCompareOK = semiNormalizedComparison(expectedOutOutputString,
603 					outOutputString, outputDirNormalizer))
604 				&& (errCompareOK = semiNormalizedComparison(expectedErrOutputString,
605 						errOutputString, outputDirNormalizer));
606 		}
607 		if (compileOK != shouldCompileOK || !compareOK) {
608 			System.out.println(getClass().getName() + '#' + getName());
609 			if (testFiles != null) {
610 				for (int i = 0; i < testFiles.length; i += 2) {
611 					System.out.print(testFiles[i]);
612 					System.out.println(" [");
613 					System.out.println(testFiles[i + 1]);
614 					System.out.println("]");
615 				}
616 			}
617 		}
618 		if (compileOK != shouldCompileOK)
619 			System.out.println(errOutputString);
620 		if (compileOK == shouldCompileOK && !compareOK) {
621 			System.out.println(
622 					    "------------ [START OUT] ------------\n"
623 					+   "------------- Expected: -------------\n"
624 					+ expectedOutOutputString
625 					+ "\n------------- but was:  -------------\n"
626 					+ outOutputString
627 					+ "\n--------- (cut and paste:) ----------\n"
628 					+ Util.displayString(outputDirNormalizer
629 							.normalized(outOutputString))
630 					+ "\n------------- [END OUT] -------------\n"
631 					+   "------------ [START ERR] ------------\n"
632 					+   "------------- Expected: -------------\n"
633 					+ expectedErrOutputString
634 					+ "\n------------- but was:  -------------\n"
635 					+ errOutputString
636 					+ "\n--------- (cut and paste:) ----------\n"
637 					+ Util.displayString(outputDirNormalizer
638 							.normalized(errOutputString))
639 					+ "\n------------- [END ERR] -------------\n");
640 		}
641 		if (shouldCompileOK)
642 			assertTrue("Unexpected problems [out: " + outOutputString + "][err: " + errOutputString + "]", compileOK);
643 		else
644 			assertFalse("Unexpected success: [out: " + outOutputString + "][err: " + errOutputString + "]", compileOK);
645 		if (!outCompareOK) {
646 			// calling assertEquals to benefit from the comparison UI
647 			// (need appropriate exception)
648 			assertEquals(
649 					"Unexpected standard output for invocation with arguments ["
650 						+ extraArguments + "]",
651 					expectedOutOutputString,
652 					outOutputString);
653 		}
654 		if (!errCompareOK) {
655 			assertEquals(
656 					"Unexpected error output for invocation with arguments ["
657 						+ extraArguments + "]",
658 					expectedErrOutputString,
659 					errOutputString);
660 		}
661 	}
662 
invokeCompiler(PrintWriter out, PrintWriter err, Object extraArguments, TestCompilationProgress compilationProgress)663 	protected boolean invokeCompiler(PrintWriter out, PrintWriter err, Object extraArguments, TestCompilationProgress compilationProgress) {
664 		try {
665 			final String[] tokenizedCommandLine = Main.tokenize((String) extraArguments);
666 			return new Main(out, err, false, null /* customDefaultOptions */, compilationProgress /* compilationProgress*/).compile(tokenizedCommandLine);
667 		} catch (RuntimeException e) {
668 			System.out.println(getClass().getName() + '#' + getName());
669 			e.printStackTrace();
670 			throw e;
671 		}
672 	}
673 
runTest(boolean shouldCompileOK, String[] testFiles, String commandLine, Matcher outOutputStringMatcher, Matcher errOutputStringMatcher, boolean shouldFlushOutputDirectory)674 	protected void runTest(boolean shouldCompileOK, String[] testFiles, String commandLine, Matcher outOutputStringMatcher, Matcher errOutputStringMatcher, boolean shouldFlushOutputDirectory) {
675 		File outputDirectory = new File(OUTPUT_DIR);
676 		if (shouldFlushOutputDirectory)
677 			Util.flushDirectoryContent(outputDirectory);
678 		try {
679 			if (!outputDirectory.isDirectory()) {
680 				outputDirectory.mkdirs();
681 			}
682 			PrintWriter sourceFileWriter;
683 			for (int i = 0; i < testFiles.length; i += 2) {
684 				String fileName = OUTPUT_DIR + File.separator + testFiles[i];
685 				File file = new File(fileName), innerOutputDirectory = file
686 						.getParentFile();
687 				if (!innerOutputDirectory.isDirectory()) {
688 					innerOutputDirectory.mkdirs();
689 				}
690 				sourceFileWriter = new PrintWriter(new FileOutputStream(file));
691 				try {
692 					sourceFileWriter.write(testFiles[i + 1]);
693 				} finally {
694 					sourceFileWriter.close();
695 				}
696 			}
697 		} catch (FileNotFoundException e) {
698 			e.printStackTrace();
699 			throw new RuntimeException(e);
700 		}
701 		String printerWritersNameRoot = OUTPUT_DIR + File.separator + testName();
702 		String outFileName = printerWritersNameRoot + "out.txt",
703 			   errFileName = printerWritersNameRoot + "err.txt";
704 		Main batchCompiler;
705 		PrintWriter out = null;
706 		PrintWriter err = null;
707 		boolean compileOK;
708 		try {
709 			try {
710 				out = new PrintWriter(new FileOutputStream(outFileName));
711 				err = new PrintWriter(new FileOutputStream(errFileName));
712 				batchCompiler = new Main(out, err, false/*systemExit*/, null/*options*/, null/*progress*/);
713 			} catch (FileNotFoundException e) {
714 				System.out.println(getClass().getName() + '#' + getName());
715 				e.printStackTrace();
716 				throw new RuntimeException(e);
717 			}
718 			try {
719 				final String[] tokenizeCommandLine = Main.tokenize(commandLine);
720 				compileOK = batchCompiler.compile(tokenizeCommandLine);
721 			} catch (RuntimeException e) {
722 				compileOK = false;
723 				System.out.println(getClass().getName() + '#' + getName());
724 				e.printStackTrace();
725 				throw e;
726 			}
727 		} finally {
728 			if (out != null)
729 				out.close();
730 			if (err != null)
731 				err.close();
732 		}
733 		String outOutputString = Util.fileContent(outFileName),
734 		       errOutputString = Util.fileContent(errFileName);
735 		boolean compareOK = false, outCompareOK = false, errCompareOK = false;
736 		String expectedErrOutputString = null, expectedOutOutputString = null;
737 		if (compileOK == shouldCompileOK) {
738 			if (outOutputStringMatcher == null) {
739 				outCompareOK = true;
740 			} else {
741 				outCompareOK = outOutputStringMatcher.match(outOutputString);
742 				expectedOutOutputString = outOutputStringMatcher.expected();
743 			}
744 			if (errOutputStringMatcher == null) {
745 				errCompareOK = true;
746 			} else {
747 				errCompareOK = errOutputStringMatcher.match(errOutputString);
748 				expectedErrOutputString = errOutputStringMatcher.expected();
749 			}
750 			compareOK = outCompareOK && errCompareOK;
751 		}
752 		if (compileOK != shouldCompileOK || !compareOK) {
753 			System.out.println(getClass().getName() + '#' + getName());
754 			for (int i = 0; i < testFiles.length; i += 2) {
755 				System.out.print(testFiles[i]);
756 				System.out.println(" [");
757 				System.out.println(testFiles[i + 1]);
758 				System.out.println("]");
759 			}
760 		}
761 		if (compileOK != shouldCompileOK)
762 			System.out.println(errOutputString);
763 		if (compileOK == shouldCompileOK && !compareOK) {
764 			System.out.println(
765 					    "------------ [START OUT] ------------\n"
766 					+   "------------- Expected: -------------\n"
767 					+ expectedOutOutputString
768 					+ "\n------------- but was:  -------------\n"
769 					+ outOutputString
770 					+ "\n--------- (cut and paste:) ----------\n"
771 					+ Util.displayString(outputDirNormalizer
772 							.normalized(outOutputString))
773 					+ "\n------------- [END OUT] -------------\n"
774 					+   "------------ [START ERR] ------------\n"
775 					+   "------------- Expected: -------------\n"
776 					+ expectedErrOutputString
777 					+ "\n------------- but was:  -------------\n"
778 					+ errOutputString
779 					+ "\n--------- (cut and paste:) ----------\n"
780 					+ Util.displayString(outputDirNormalizer
781 							.normalized(errOutputString))
782 					+ "\n------------- [END ERR] -------------\n");
783 		}
784 		if (shouldCompileOK)
785 			assertTrue("Unexpected problems: " + errOutputString, compileOK);
786 		else
787 			assertTrue("Unexpected success: " + errOutputString, !compileOK);
788 		if (!outCompareOK) {
789 			// calling assertEquals to benefit from the comparison UI
790 			// (need appropriate exception)
791 			assertEquals(
792 					"Unexpected standard output for invocation with arguments ["
793 						+ commandLine + "]",
794 					expectedOutOutputString,
795 					outOutputString);
796 		}
797 		if (!errCompareOK) {
798 			assertEquals(
799 					"Unexpected error output for invocation with arguments ["
800 						+ commandLine + "]",
801 					expectedErrOutputString,
802 					errOutputString);
803 		}
804 	}
805 
runClasspathTest(String classpathInput, String[] expectedClasspathEntries, String expectedError)806 	protected void runClasspathTest(String classpathInput, String[] expectedClasspathEntries, String expectedError) {
807 		File outputDirectory = new File(OUTPUT_DIR);
808 		if (!outputDirectory.isDirectory()) {
809 			outputDirectory.mkdirs();
810 		}
811 		ArrayList<FileSystem.Classpath> paths = new ArrayList<>(Main.DEFAULT_SIZE_CLASSPATH);
812 		try {
813 			(new Main(new PrintWriter(System.out), new PrintWriter(System.err), true/*systemExit*/, null/*options*/, null/*progress*/)).
814 				processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, classpathInput, null /* customEncoding */, true /* isSourceOnly */, false /* rejectDestinationPathOnJars*/);
815 		} catch (IllegalArgumentException e) {
816 			// e.printStackTrace();
817 			if (expectedError == null) {
818 				fail("unexpected invalid input exception: " + e.getMessage());
819 			} else if (! expectedError.equals(e.getMessage())) {
820 				System.out.println("\"" + e.getMessage() + "\"");
821 				assertEquals(expectedError, e.getMessage());
822 			}
823 			return;
824 		}
825 		if (expectedError == null) {
826 			int l = paths.size();
827 			assertEquals("unexpected classpaths entries number: ",
828 					expectedClasspathEntries == null ? 0 : expectedClasspathEntries.length / 3, l);
829 			for (int i = 0, j = 0; i < l ; i++) {
830 				ClasspathLocation result = (ClasspathLocation) paths.get(i);
831 				String expected = expectedClasspathEntries[j++];
832 				String actual = result.toString();
833 				if (! actual.equals("ClasspathDirectory " + expected + File.separator) &&
834 						! actual.equals("Classpath for jar file " + expected)) {
835 					assertEquals("dir/jar " + expected, actual);
836 				}
837 				expected = expectedClasspathEntries[j++];
838 				if (result.accessRuleSet == null) {
839 					assertNull("actual access rule is null instead of <" + expected +">", expected);
840 				} else if (! result.accessRuleSet.toString(false).
841 						startsWith("AccessRuleSet " + expected)) {
842 					System.out.println("\"" + result.accessRuleSet.toString(false) + "\"");
843 					fail("inappropriate rules (expected " + expected +
844 						", got " + result.accessRuleSet.toString(false));
845 				}
846 				expected = expectedClasspathEntries[j++];
847 				if (expected == null) {
848 					assertNull(result.destinationPath);
849 				} else if (expected == Main.NONE &&
850 						result.destinationPath != Main.NONE) {
851 					fail("expected 'none' output directory");
852 				} else if (! expected.equals(result.destinationPath)) {
853 					System.out.println("\"" + result.destinationPath + "\"");
854 					assertEquals(expected, result.destinationPath);
855 				}
856 			}
857 		} else {
858 			fail("missing error: " + expectedError);
859 		}
860 	}
861 
862 	/**
863 	 * Check that no line of message extends beyond width columns. Tabs count for
864 	 * 4 characters.
865 	 * @param message the message to check
866 	 * @param width the maximum number of columns for the message
867 	 */
checkWidth(String message, int width)868 	protected void checkWidth(String message, int width) {
869 		BufferedReader reader = new BufferedReader(
870 				new StringReader(message.replaceAll("\t", "    ")));
871 		String line;
872 		try {
873 			while ((line = reader.readLine()) != null) {
874 				assertTrue("line exceeds " + width + "characters: " + line,
875 					line.length() <= width);
876 			}
877 		} catch (IOException e) {
878 			// should never happen on a StringReader
879 		}
880 	}
881 
equals(String a, String b)882 	private static boolean equals(String a, String b) {
883 		StringBuffer aBuffer = new StringBuffer(a), bBuffer = new StringBuffer(b);
884 		int length = aBuffer.length(), bLength;
885 		boolean result = true;
886 		if (length != (bLength = bBuffer.length())) {
887 			System.err.println("a and b lengths differ");
888 			if (length > bLength) {
889 				length = bLength;
890 			}
891 			result = false;
892 		}
893 		for (int i = 0; i < length; i++)
894 			if (aBuffer.charAt(i) != bBuffer.charAt(i)) {
895 				int beforeStart = i - 5, beforeEnd = i - 1, afterStart = i + 1, afterEnd = i + 5;
896 				if (beforeStart < 0) {
897 					beforeStart = 0;
898 					if (beforeEnd < 0)
899 						beforeEnd = 0;
900 				}
901 				if (afterEnd >= length) {
902 					afterEnd = length - 1;
903 					if (afterStart >= length)
904 						afterStart = length - 1;
905 				}
906 				System.err.println("a and b differ at rank: " + i
907 						+ "\na: ..." + aBuffer.substring(beforeStart, beforeEnd)
908 							+ "<" + aBuffer.charAt(i) + ">"
909 							+ aBuffer.substring(afterStart, afterEnd) + "..."
910 						+ "\nb: ..." + bBuffer.substring(beforeStart, beforeEnd)
911 							+ "<" + bBuffer.charAt(i) + ">"
912 							+ bBuffer.substring(afterStart, afterEnd) + "...");
913 				return false;
914 			}
915 		return result; // may be false if one of the strings equals the beginning
916 		               // of the other one, which is longer anyway
917 	}
918 
919 	/**
920 	 * Return true if and only if the two strings passed as parameters compare
921 	 * equal, modulo the transformation of the second string by a normalizer
922 	 * passed in parameter. This is meant to erase the variations of subparts of
923 	 * the compared strings in function of the test machine, the user account,
924 	 * etc.
925 	 *
926 	 * @param keep
927 	 *            the first string to compare, gets compared as it is
928 	 * @param normalize
929 	 *            the second string to compare, passed through the normalizer
930 	 *            before comparison
931 	 * @param normalizer
932 	 *            the transformation applied to normalize
933 	 * @return true if keep and normalize compare equal after normalize has been
934 	 *         normalized
935 	 */
semiNormalizedComparison(String keep, String normalize, Normalizer normalizer)936 	protected boolean semiNormalizedComparison(String keep, String normalize, Normalizer normalizer) {
937 		if (keep == null)
938 			return normalize == null;
939 		if (normalize == null)
940 			return false;
941 		// return keep.equals(normalizer.normalized(normalize));
942 		return equals(keep, normalizer.normalized(normalize));
943 	}
944 
945 }
946