com.github.anba.es6draft.util.TestGlobals.java Source code

Java tutorial

Introduction

Here is the source code for com.github.anba.es6draft.util.TestGlobals.java

Source

/**
 * 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.util;

import static java.util.Collections.emptyList;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.apache.commons.configuration.Configuration;
import org.junit.rules.ExternalResource;

import com.github.anba.es6draft.Script;
import com.github.anba.es6draft.compiler.Compiler;
import com.github.anba.es6draft.parser.Parser;
import com.github.anba.es6draft.runtime.ExecutionContext;
import com.github.anba.es6draft.runtime.Realm;
import com.github.anba.es6draft.runtime.World;
import com.github.anba.es6draft.runtime.internal.CompatibilityOption;
import com.github.anba.es6draft.runtime.internal.Console;
import com.github.anba.es6draft.runtime.internal.ObjectAllocator;
import com.github.anba.es6draft.runtime.internal.RuntimeContext;
import com.github.anba.es6draft.runtime.internal.ScriptCache;
import com.github.anba.es6draft.runtime.internal.ScriptLoader;
import com.github.anba.es6draft.runtime.internal.Source;
import com.github.anba.es6draft.runtime.modules.MalformedNameException;
import com.github.anba.es6draft.runtime.modules.ModuleRecord;
import com.github.anba.es6draft.runtime.modules.ResolutionException;
import com.github.anba.es6draft.runtime.modules.SourceIdentifier;
import com.github.anba.es6draft.runtime.objects.GlobalObject;

/**
 * {@link ExternalResource} sub-class to facilitate creation of {@link GlobalObject} instances
 */
public class TestGlobals<GLOBAL extends GlobalObject, TEST extends TestInfo> extends ExternalResource {
    private static final String DEFAULT_MODE = "web-compatibility";
    private static final String DEFAULT_VERSION = CompatibilityOption.Version.ECMAScript2016.name();
    private static final String DEFAULT_STAGE = CompatibilityOption.Stage.Finished.name();
    private static final List<String> DEFAULT_FEATURES = emptyList();

    private final Configuration configuration;
    private final ObjectAllocator<GLOBAL> allocator;
    private final BiFunction<RuntimeContext, ScriptLoader, TestModuleLoader<?>> moduleLoader;
    private EnumSet<CompatibilityOption> options;
    private ScriptCache scriptCache;
    private List<Script> scripts;
    private PreloadModules modules;

    public TestGlobals(Configuration configuration, ObjectAllocator<GLOBAL> allocator) {
        this(configuration, allocator, TestFileModuleLoader::new);
    }

    public TestGlobals(Configuration configuration, ObjectAllocator<GLOBAL> allocator,
            BiFunction<RuntimeContext, ScriptLoader, TestModuleLoader<?>> moduleLoader) {
        this.configuration = configuration;
        this.allocator = allocator;
        this.moduleLoader = moduleLoader;
    }

    protected ExecutorService getExecutor() {
        return null;
    }

    protected EnumSet<CompatibilityOption> getOptions() {
        return EnumSet.copyOf(options);
    }

    protected EnumSet<Parser.Option> getParserOptions() {
        return EnumSet.noneOf(Parser.Option.class);
    }

    protected EnumSet<Compiler.Option> getCompilerOptions() {
        return EnumSet.noneOf(Compiler.Option.class);
    }

    protected Path getBaseDirectory() {
        return Resources.getTestSuitePath(configuration);
    }

    protected RuntimeContext createContext() {
        /* @formatter:off */
        return new RuntimeContext.Builder().setBaseDirectory(getBaseDirectory()).setExecutor(getExecutor())
                .setOptions(getOptions()).setParserOptions(getParserOptions())
                .setCompilerOptions(getCompilerOptions()).setScriptCache(scriptCache)
                .setWorkerErrorReporter(this::workerErrorReporter).build();
        /* @formatter:on */
    }

    protected RuntimeContext createContext(Console console, TEST test) {
        /* @formatter:off */
        return new RuntimeContext.Builder().setLocale(getLocale(test)).setTimeZone(getTimeZone(test))
                .setGlobalAllocator(allocator).setModuleLoader(moduleLoader).setBaseDirectory(test.getBaseDir())
                .setExecutor(getExecutor()).setConsole(console).setOptions(getOptions())
                .setParserOptions(getParserOptions()).setCompilerOptions(getCompilerOptions())
                .setScriptCache(scriptCache).setWorkerErrorReporter(this::workerErrorReporter).build();
        /* @formatter:on */
    }

    protected Locale getLocale(TEST test) {
        return Locale.getDefault();
    }

    protected TimeZone getTimeZone(TEST test) {
        return TimeZone.getDefault();
    }

    protected void workerErrorReporter(ExecutionContext cx, Throwable t) {
        t.printStackTrace();
    }

    protected static ThreadPoolExecutor createDefaultSharedExecutor() {
        int coreSize = 4;
        int maxSize = 12;
        long timeout = 60L;
        int queueCapacity = 10;
        return new ThreadPoolExecutor(coreSize, maxSize, timeout, TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(queueCapacity), new ThreadPoolExecutor.CallerRunsPolicy());
    }

    @Override
    protected void before() throws Throwable {
        if (!Resources.isEnabled(configuration)) {
            // skip initialization if test suite not enabled
            return;
        }
        scriptCache = new ScriptCache();

        // read options ...
        EnumSet<CompatibilityOption> compatibilityOptions = EnumSet.noneOf(CompatibilityOption.class);
        optionsFromMode(compatibilityOptions, configuration.getString("mode", DEFAULT_MODE));
        optionsFromVersion(compatibilityOptions, configuration.getString("version", DEFAULT_VERSION));
        optionsFromStage(compatibilityOptions, configuration.getString("stage", DEFAULT_STAGE));
        optionsFromFeatures(compatibilityOptions, configuration.getList("features", DEFAULT_FEATURES));
        options = compatibilityOptions;

        // pre-compile initialization scripts and modules
        scripts = compileScripts();
        modules = compileModules();
    }

    public final GLOBAL newGlobal(Console console, TEST test)
            throws MalformedNameException, ResolutionException, IOException, URISyntaxException {
        RuntimeContext context = createContext(console, test);
        World world = new World(context);
        Realm realm = world.newInitializedRealm();

        // Evaluate additional initialization scripts and modules
        TestModuleLoader<?> moduleLoader = (TestModuleLoader<?>) world.getModuleLoader();
        for (ModuleRecord module : modules.allModules) {
            moduleLoader.defineFromTemplate(module, realm);
        }
        for (ModuleRecord module : modules.mainModules) {
            ModuleRecord testModule = moduleLoader.get(module.getSourceCodeId(), realm);
            testModule.instantiate();
            testModule.evaluate();
        }
        for (Script script : scripts) {
            script.evaluate(realm);
        }

        @SuppressWarnings("unchecked")
        GLOBAL global = (GLOBAL) realm.getGlobalObject();
        return global;
    }

    public void release(GLOBAL global) {
        if (global != null) {
            global.getRuntimeContext().getExecutor().shutdown();
            global.getRuntimeContext().getWorkerExecutor().shutdown();
        }
    }

    private static void optionsFromMode(EnumSet<CompatibilityOption> options, String mode) {
        switch (mode) {
        case "moz-compatibility":
            options.addAll(CompatibilityOption.MozCompatibility());
            break;
        case "web-compatibility":
            options.addAll(CompatibilityOption.WebCompatibility());
            break;
        case "strict-compatibility":
            options.addAll(CompatibilityOption.StrictCompatibility());
            break;
        default:
            throw new IllegalArgumentException(String.format("Unsupported mode: '%s'", mode));
        }
    }

    private static void optionsFromVersion(EnumSet<CompatibilityOption> options, String version) {
        options.addAll(Arrays.stream(CompatibilityOption.Version.values()).filter(v -> {
            return v.name().equalsIgnoreCase(version);
        }).findAny().map(CompatibilityOption::Version).orElseThrow(IllegalArgumentException::new));
    }

    private static void optionsFromStage(EnumSet<CompatibilityOption> options, String stage) {
        options.addAll(Arrays.stream(CompatibilityOption.Stage.values()).filter(s -> {
            if (stage.length() == 1 && Character.isDigit(stage.charAt(0))) {
                return s.getLevel() == Character.digit(stage.charAt(0), 10);
            } else {
                return s.name().equalsIgnoreCase(stage);
            }
        }).findAny().map(CompatibilityOption::Stage).orElseThrow(IllegalArgumentException::new));
    }

    private static void optionsFromFeatures(EnumSet<CompatibilityOption> options, List<Object> features) {
        streamNonEmpty(features).map(TestGlobals::optionFromFeature).forEach(options::add);
    }

    private static CompatibilityOption optionFromFeature(String feature) {
        return Arrays.stream(CompatibilityOption.values()).filter(o -> {
            return o.name().equalsIgnoreCase(feature);
        }).findAny().orElseThrow(IllegalArgumentException::new);
    }

    private List<Script> compileScripts() throws IOException {
        List<?> scriptNames = configuration.getList("scripts", emptyList());
        if (scriptNames.isEmpty()) {
            return Collections.emptyList();
        }
        Path basedir = getBaseDirectory();
        RuntimeContext context = createContext();
        ScriptLoader scriptLoader = new ScriptLoader(context);
        ArrayList<Script> scripts = new ArrayList<>();
        for (String scriptName : nonEmpty(scriptNames)) {
            Source source = new Source(Resources.resourcePath(scriptName, basedir), scriptName, 1);
            Script script = scriptLoader.script(source, Resources.resource(scriptName, basedir));
            scripts.add(script);
        }
        return scripts;
    }

    private PreloadModules compileModules() throws IOException, MalformedNameException {
        List<?> moduleNames = configuration.getList("modules", emptyList());
        if (moduleNames.isEmpty()) {
            return new PreloadModules(Collections.<ModuleRecord>emptyList(), Collections.<ModuleRecord>emptyList());
        }
        RuntimeContext context = createContext();
        ScriptLoader scriptLoader = new ScriptLoader(context);
        TestModuleLoader<?> moduleLoader = this.moduleLoader.apply(context, scriptLoader);
        ArrayList<ModuleRecord> modules = new ArrayList<>();
        for (String moduleName : nonEmpty(moduleNames)) {
            SourceIdentifier moduleId = moduleLoader.normalizeName(moduleName, null);
            modules.add(moduleLoader.load(moduleId));
        }
        return new PreloadModules(modules, moduleLoader.getModules());
    }

    private static final class PreloadModules {
        private final List<ModuleRecord> mainModules;
        private final Collection<ModuleRecord> allModules;

        PreloadModules(List<ModuleRecord> modules, Collection<? extends ModuleRecord> requires) {
            assert modules.size() <= requires.size();
            this.mainModules = Collections.unmodifiableList(modules);
            this.allModules = Collections.<ModuleRecord>unmodifiableCollection(requires);
        }
    }

    private static Iterable<String> nonEmpty(List<?> c) {
        return () -> streamNonEmpty(c).iterator();
    }

    private static Stream<String> streamNonEmpty(List<?> c) {
        return c.stream().filter(Objects::nonNull).map(Object::toString).filter(not(String::isEmpty));
    }

    private static <T> Predicate<T> not(Predicate<T> p) {
        return p.negate();
    }
}