/**
* Copyright (c) 2012-2016 André Bargull
* Alle Rechte vorbehalten / All Rights Reserved. Use is subject to license terms.
*
* <https://github.com/anba/es6draft>
*/
package com.github.anba.es6draft.mozilla;
import static com.github.anba.es6draft.runtime.AbstractOperations.ToBoolean;
import static com.github.anba.es6draft.util.Resources.loadConfiguration;
import static com.github.anba.es6draft.util.Resources.loadTests;
import static org.junit.Assume.assumeTrue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.configuration.Configuration;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.rules.ExpectedException;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.junit.runners.Parameterized.UseParametersRunnerFactory;
import org.junit.runners.model.MultipleFailureException;
import com.github.anba.es6draft.runtime.internal.Properties;
import com.github.anba.es6draft.runtime.internal.Source;
import com.github.anba.es6draft.runtime.internal.Strings;
import com.github.anba.es6draft.util.NullConsole;
import com.github.anba.es6draft.util.Parallelized;
import com.github.anba.es6draft.util.ParameterizedRunnerFactory;
import com.github.anba.es6draft.util.TestAssertions;
import com.github.anba.es6draft.util.TestConfiguration;
import com.github.anba.es6draft.util.TestGlobals;
import com.github.anba.es6draft.util.TestInfo;
import com.github.anba.es6draft.util.rules.ExceptionHandlers.IgnoreExceptionHandler;
import com.github.anba.es6draft.util.rules.ExceptionHandlers.ScriptExceptionHandler;
import com.github.anba.es6draft.util.rules.ExceptionHandlers.StandardErrorHandler;
import com.github.anba.es6draft.util.rules.ExceptionHandlers.StopExecutionHandler;
/**
* Test suite for the Mozilla js-tests.
*/
@RunWith(Parallelized.class)
@UseParametersRunnerFactory(ParameterizedRunnerFactory.class)
@TestConfiguration(name = "mozilla.test.jstests", file = "resource:/test-configuration.properties")
public final class MozillaJSTest {
private static final Configuration configuration = loadConfiguration(MozillaJSTest.class);
@Parameters(name = "{0}")
public static List<MozTest> suiteValues() throws IOException {
return loadTests(configuration, MozillaJSTest::createTest);
}
@BeforeClass
public static void setUpClass() throws IOException {
MozTestGlobalObject.testLoadInitializationScript();
}
@ClassRule
public static TestGlobals<MozTestGlobalObject, TestInfo> globals = new TestGlobals<>(configuration,
MozTestGlobalObject::new);
@Rule
public Timeout maxTime = new Timeout(120, TimeUnit.SECONDS);
@Rule
public ErrorCollector collector = new ErrorCollector() {
@Override
protected void verify() throws Throwable {
// Ignore collected errors if test is marked as random
if (!moztest.random) {
super.verify();
}
}
};
@Rule
public StandardErrorHandler errorHandler = StandardErrorHandler.none();
@Rule
public ScriptExceptionHandler exceptionHandler = ScriptExceptionHandler.none();
@Rule
public IgnoreExceptionHandler ignoreHandler = IgnoreExceptionHandler.none();
@Rule
public StopExecutionHandler stopHandler = new StopExecutionHandler();
@Rule
public ExpectedException expected = ExpectedException.none();
@Parameter(0)
public MozTest moztest;
private static final class MozTest extends TestInfo {
List<Entry<Condition, String>> conditions = new ArrayList<>();
boolean random = false;
boolean negative = false;
public MozTest(Path basedir, Path script) {
super(basedir, script);
}
void addCondition(Condition c, String s) {
conditions.add(new SimpleEntry<>(c, s));
}
}
private enum Condition {
FailsIf, SkipIf, RandomIf
}
private MozTestGlobalObject global;
@Before
public void setUp() throws Throwable {
assumeTrue("Test disabled", moztest.isEnabled());
global = globals.newGlobal(new NullConsole(), moztest);
global.createGlobalProperties(new Print(), Print.class);
exceptionHandler.setExecutionContext(global.getRealm().defaultContext());
// Apply scripted conditions
scriptConditions();
// Filter disabled tests (may have changed after applying scripted conditions)
assumeTrue("Test disabled", moztest.isEnabled());
if (moztest.random) {
// Results from random tests are simply ignored...
ignoreHandler.match(IgnoreExceptionHandler.defaultMatcher());
} else if (moztest.negative) {
expected.expect(
Matchers.either(StandardErrorHandler.defaultMatcher()).or(ScriptExceptionHandler.defaultMatcher())
.or(Matchers.instanceOf(MultipleFailureException.class)));
} else {
errorHandler.match(StandardErrorHandler.defaultMatcher());
exceptionHandler.match(ScriptExceptionHandler.defaultMatcher());
}
}
@After
public void tearDown() {
globals.release(global);
}
@Test
public void runTest() throws Throwable {
// Load and execute shell.js files
for (Path shell : shellJS(moztest)) {
global.include(shell);
}
// Evaluate actual test-script
global.eval(moztest.getScript(), moztest.toFile());
// Wait for pending tasks to finish
global.getRealm().getWorld().runEventLoop();
}
/**
* Returns an {@link Iterable} of 'shell.js'-{@link Path}s
*/
private static Iterable<Path> shellJS(MozTest test) {
// Add 'shell.js' files from each directory
List<Path> files = new ArrayList<>();
Path testDir = test.getBaseDir();
Path dir = Paths.get("");
for (Iterator<Path> iterator = test.getScript().iterator(); iterator
.hasNext(); dir = dir.resolve(iterator.next())) {
Path f = testDir.resolve(dir.resolve("shell.js"));
if (Files.exists(f)) {
files.add(f);
}
}
return files;
}
private void scriptConditions() {
for (Entry<Condition, String> entry : moztest.conditions) {
if (!evaluateCondition(entry)) {
continue;
}
switch (entry.getKey()) {
case FailsIf:
moztest.negative = true;
break;
case RandomIf:
moztest.random = true;
break;
case SkipIf:
moztest.setEnabled(false);
break;
default:
throw new IllegalStateException();
}
}
}
private boolean evaluateCondition(Entry<Condition, String> entry) {
String code = condition(entry.getValue());
Object value = global.eval(new Source("@evaluate", 1), code);
return ToBoolean(value);
}
private static String condition(String c) {
StringBuilder sb = new StringBuilder();
sb.append("!!(function(){\n");
sb.append("var xulRuntime = {OS: 'Linux', XPCOMABI: 'x86_64-gcc3', shell: true};\n");
sb.append("var browserIsRemote = false;\n");
sb.append("var isDebugBuild = false;\n");
sb.append("var Android = false;\n");
sb.append("return (").append(c).append(");");
sb.append("})();");
return sb.toString();
}
public final class Print {
@Properties.Function(name = "print", arity = 1)
public void print(String... messages) {
String message = Strings.concatWith(' ', messages);
if (message.startsWith(" FAILED! ")) {
// Collect all failures instead of calling fail() directly.
collector.addError(TestAssertions.newAssertionError(message));
}
}
}
private static final Pattern testInfoPattern = Pattern.compile("//\\s*\\|(.+?)\\|\\s*(.*)");
private static BiFunction<Path, Iterator<String>, MozTest> createTest(Path basedir) {
return (file, lines) -> {
MozTest test = new MozTest(basedir, file);
// Negative tests end with "-n"
if (file.getFileName().toString().endsWith("-n.js")) {
test.negative = true;
}
String line = lines.next();
Matcher m = testInfoPattern.matcher(line);
if (!m.matches()) {
// Ignore if pattern invalid or not present
return test;
}
if (!"reftest".equals(m.group(1))) {
System.err.printf("invalid tag '%s' in line: %s\n", m.group(1), line);
return test;
}
String content = m.group(2);
for (String p : split(content)) {
if (p.equals("fails")) {
test.negative = true;
} else if (p.equals("skip")) {
test.setEnabled(false);
} else if (p.equals("random")) {
test.random = true;
} else if (p.equals("slow")) {
// Don't run slow tests
test.setEnabled(false);
} else if (p.equals("silentfail")) {
// Ignore for now...
} else if (p.startsWith("fails-if")) {
test.addCondition(Condition.FailsIf, p.substring("fails-if".length()));
} else if (p.startsWith("skip-if")) {
test.addCondition(Condition.SkipIf, p.substring("skip-if".length()));
} else if (p.startsWith("random-if")) {
test.addCondition(Condition.RandomIf, p.substring("random-if".length()));
} else if (p.startsWith("asserts-if")) {
// Ignore for now...
} else if (p.startsWith("require-or")) {
// Ignore for now...
} else {
System.err.printf("invalid manifest line: %s\n", p);
}
}
return test;
};
}
private static String[] split(String line) {
final String comment = "--";
final String ws = "[ \t\n\r\f\013]+";
// Remove comment if any
int k = line.indexOf(comment);
if (k != -1) {
line = line.substring(0, k);
}
// Split at whitespace
return line.trim().split(ws);
}
}