| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| package com.google.dart.corelib; |
| |
| import com.google.dart.compiler.AnalysisError; |
| import com.google.dart.compiler.CommandLineOptions.CompilerOptions; |
| import com.google.dart.compiler.CompilerConfiguration; |
| import com.google.dart.compiler.DartArtifactProvider; |
| import com.google.dart.compiler.DartCompilationError; |
| import com.google.dart.compiler.DartCompiler; |
| import com.google.dart.compiler.DartCompilerListener; |
| import com.google.dart.compiler.DefaultCompilerConfiguration; |
| import com.google.dart.compiler.DefaultDartArtifactProvider; |
| import com.google.dart.compiler.DefaultLibrarySource; |
| import com.google.dart.compiler.ErrorSeverity; |
| import com.google.dart.compiler.LibrarySource; |
| import com.google.dart.compiler.Source; |
| import com.google.dart.compiler.SubSystem; |
| import com.google.dart.compiler.UrlLibrarySource; |
| |
| import junit.framework.AssertionFailedError; |
| import junit.framework.Test; |
| import junit.framework.TestCase; |
| import junit.framework.TestSuite; |
| |
| import org.kohsuke.args4j.CmdLineException; |
| import org.kohsuke.args4j.CmdLineParser; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.CharArrayReader; |
| import java.io.CharArrayWriter; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| import java.io.Writer; |
| import java.net.URI; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Enumeration; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Wrapper around test that are shared between compiler and runtime (test.py). |
| * <p> |
| * Sometimes, when fixing a known issue, a test that was previously crashing or failing may start |
| * passing. This will show up as a test failure because the status file has wrong information. |
| * Please update the status file. |
| */ |
| public abstract class SharedTestCase extends TestCase { |
| private static final Pattern SEPARATOR = Pattern.compile("\\t"); |
| |
| private final Set<String> outcomes; |
| private final boolean isNegative; |
| private final String[] arguments; |
| private final boolean regularCompile; |
| private final AtomicInteger compilationErrorCount = new AtomicInteger(0); |
| private final AtomicInteger typeErrorCount = new AtomicInteger(0); |
| private final AtomicInteger warningCount = new AtomicInteger(0); |
| |
| SharedTestCase(String name, Set<String> outcomes, boolean isNegative, boolean regularCompile, |
| String[] arguments) { |
| super(name); |
| this.outcomes = outcomes; |
| this.isNegative = isNegative; |
| this.regularCompile = regularCompile; |
| this.arguments = arguments; |
| } |
| |
| /** |
| * This constructor is provided for compatibility with Eclipse (for running |
| * a single test case). |
| */ |
| public SharedTestCase(String name) { |
| super(name = scrubName(name)); |
| TestSuite suite = SharedTests.suite(); |
| Enumeration<Test> tests = suite.tests(); |
| SharedTestCase test = null; |
| while (tests.hasMoreElements()) { |
| SharedTestCase current = (SharedTestCase) tests.nextElement(); |
| if (current.getScrubbedName().equals(name)) { |
| test = current; |
| break; |
| } |
| } |
| if (test == null) { |
| throw new IllegalArgumentException("Test '" +name + "' was not found."); |
| } |
| this.outcomes = test.outcomes; |
| this.isNegative = test.isNegative; |
| this.regularCompile = test.regularCompile; |
| this.arguments = test.arguments; |
| } |
| |
| private static String scrubName(String name) { |
| int i = name.indexOf('['); |
| if (i == -1) { |
| return name; |
| } |
| return name.substring(0, i - 1); |
| } |
| |
| @Override |
| public String getName() { |
| List<String> remarks = new ArrayList<String>(); |
| if (isNegative) { |
| remarks.add("negative"); |
| } |
| for (String outcome : outcomes) { |
| if (!outcome.equals("pass")) { |
| remarks.add(outcome); |
| } |
| } |
| if (remarks.isEmpty()) { |
| return super.getName(); |
| } else { |
| return super.getName() + " " + remarks; |
| } |
| } |
| |
| String getScrubbedName() { |
| return super.getName(); |
| } |
| |
| @Override |
| public void runBare() { |
| ByteArrayOutputStream byteOutput = new ByteArrayOutputStream(); |
| PrintStream outputStream = new PrintStream(byteOutput); |
| try { |
| invokeCompiler(); |
| } catch (AnalysisError e) { |
| outputStream.close(); |
| analyzeError(e, byteOutput.toString()); |
| return; |
| } catch (Throwable t) { |
| outputStream.close(); |
| analyzeCrash(t); |
| return; |
| } |
| outputStream.close(); |
| analyzeNormalCompletion(); |
| } |
| |
| private void invokeCompiler() throws CmdLineException, IOException, AnalysisError { |
| CmdLineParser cmdLineParser = null; |
| CompilerOptions compilerOptions = new CompilerOptions(); |
| cmdLineParser = new CmdLineParser(compilerOptions); |
| cmdLineParser.parseArgument(arguments); |
| CompilerConfiguration config = new DefaultCompilerConfiguration(compilerOptions); |
| DartArtifactProvider provider = getArtifactProvider(config.getOutputDirectory()); |
| DartCompilerListener listener = getListener(); |
| List<String> sourceFiles = compilerOptions.getSourceFiles(); |
| assertEquals("incorrect number of source files " + sourceFiles, 1, sourceFiles.size()); |
| File sourceFile = new File(sourceFiles.get(0)); |
| LibrarySource lib; |
| if (sourceFile.getName().endsWith(".dart")) { |
| lib = new DefaultLibrarySource(sourceFile, null); |
| } else { |
| lib = new UrlLibrarySource(sourceFile); |
| } |
| DartCompiler.compileLib(lib, config, provider, listener); |
| if (compilationErrorCount.get() != 0 || typeErrorCount.get() != 0 || warningCount.get() != 0) { |
| throw new AnalysisError(sourceFile.getPath()); |
| } |
| } |
| |
| private DartArtifactProvider getArtifactProvider(File outputDirectory) { |
| final DartArtifactProvider provider = new DefaultDartArtifactProvider(outputDirectory); |
| return new DartArtifactProvider() { |
| ConcurrentHashMap<URI, CharArrayWriter> artifacts = |
| new ConcurrentHashMap<URI, CharArrayWriter>(); |
| |
| @Override |
| public boolean isOutOfDate(Source source, Source base, String extension) { |
| return true; |
| } |
| |
| @Override |
| public Writer getArtifactWriter(Source source, String part, String extension) { |
| URI uri = getArtifactUri(source, part, extension); |
| CharArrayWriter writer = new CharArrayWriter(); |
| CharArrayWriter existing = artifacts.putIfAbsent(uri, writer); |
| return (existing == null) ? writer : existing; |
| } |
| |
| |
| @Override |
| public URI getArtifactUri(Source source, String part, String extension) { |
| return provider.getArtifactUri(source, part, extension); |
| } |
| |
| @Override |
| public Reader getArtifactReader(Source source, String part, String extension) |
| throws IOException { |
| URI uri = getArtifactUri(source, part, extension); |
| CharArrayWriter writer = artifacts.get(uri); |
| if (writer != null) { |
| return new CharArrayReader(writer.toCharArray()); |
| } |
| return provider.getArtifactReader(source, part, extension); |
| } |
| }; |
| } |
| |
| private DartCompilerListener getListener() { |
| DartCompilerListener listener = new DartCompilerListener.Empty() { |
| @Override |
| public void onError(DartCompilationError event) { |
| if (event.getErrorCode().getSubSystem() == SubSystem.STATIC_TYPE) { |
| typeErrorCount.incrementAndGet(); |
| } else if (event.getErrorCode().getErrorSeverity() == ErrorSeverity.ERROR) { |
| compilationErrorCount.incrementAndGet(); |
| } else if (event.getErrorCode().getErrorSeverity() == ErrorSeverity.WARNING) { |
| warningCount.incrementAndGet(); |
| } |
| maybeThrow(event); |
| } |
| |
| private void maybeThrow(DartCompilationError event) { |
| if (isNegative) { |
| return; |
| } |
| if (outcomes.contains("pass")) { |
| // It is easier to debug a failing regular test if we throw an exception. |
| throw new AssertionError(event); |
| } |
| } |
| }; |
| return listener; |
| } |
| |
| private void analyzeNormalCompletion() { |
| if (isNegative) { |
| if (!outcomes.contains("fail")) { |
| fail("Negative test didn't cause an error"); |
| } |
| } else { |
| if (!outcomes.contains("pass")) { |
| fail("Test passed unexpectly, please update status file"); |
| } |
| } |
| } |
| |
| private void analyzeCrash(Throwable t) { |
| if (outcomes.contains("crash")) { |
| return; |
| } |
| String message = outcomes.contains("fail") ? "Failing test crashed" : "Test crashed unexpectly"; |
| AssertionFailedError error = new AssertionFailedError(message); |
| error.initCause(t); |
| throw error; |
| } |
| |
| private void analyzeError(AnalysisError e, String log) { |
| if (isNegative) { |
| if (!outcomes.contains("pass")) { |
| fail("Negative test is passing, please update status file"); |
| } |
| } else { |
| if (!outcomes.contains("fail")) { |
| fail(log + e.getLocalizedMessage()); |
| } |
| } |
| } |
| |
| static Test getInstance(String line, boolean regularCompile) { |
| String[] fields = SEPARATOR.split(line); |
| if (fields.length <= 3) { |
| fail("Line is not parsable: " + line); |
| } |
| String name = fields[0]; |
| Set<String> outcomes = new HashSet<String>(Arrays.<String>asList(fields[1].split(","))); |
| boolean isNegative = fields[2].equals("True"); |
| String[] arguments = new String[fields.length - 3]; |
| System.arraycopy(fields, 3, arguments, 0, arguments.length); |
| return new SharedTestCase(name, outcomes, isNegative, regularCompile, arguments) {}; |
| } |
| } |