| // Copyright (c) 2019, the R8 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.android.tools.r8.rewrite; | |
| import static org.hamcrest.CoreMatchers.anyOf; | |
| import static org.hamcrest.CoreMatchers.containsString; | |
| import static org.hamcrest.CoreMatchers.equalTo; | |
| import static org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertNotNull; | |
| import com.android.tools.r8.CompilationFailedException; | |
| import com.android.tools.r8.DataEntryResource; | |
| import com.android.tools.r8.TestParameters; | |
| import com.android.tools.r8.TestParametersCollection; | |
| import com.android.tools.r8.origin.Origin; | |
| import com.android.tools.r8.utils.StreamUtils; | |
| import com.android.tools.r8.utils.StringUtils; | |
| import java.io.IOException; | |
| import java.io.Reader; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.List; | |
| import java.util.concurrent.ExecutionException; | |
| import java.util.zip.ZipEntry; | |
| import java.util.zip.ZipFile; | |
| import javax.script.Bindings; | |
| import javax.script.ScriptContext; | |
| import javax.script.ScriptEngine; | |
| import javax.script.ScriptEngineFactory; | |
| import javax.script.ScriptEngineManager; | |
| import javax.script.ScriptException; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.Parameterized; | |
| @RunWith(Parameterized.class) | |
| public class ScriptEngineTest extends ScriptEngineTestBase { | |
| private final TestParameters parameters; | |
| @Parameterized.Parameters(name = "{0}") | |
| public static TestParametersCollection data() { | |
| return getTestParameters().withAllRuntimesAndApiLevels().build(); | |
| } | |
| public ScriptEngineTest(TestParameters parameters) { | |
| this.parameters = parameters; | |
| } | |
| @Test | |
| public void test() throws IOException, CompilationFailedException, ExecutionException { | |
| Path path = temp.newFile("out.zip").toPath(); | |
| testForR8(parameters.getBackend()) | |
| .addInnerClasses(ScriptEngineTest.class) | |
| .addKeepMainRule(TestClass.class) | |
| .applyIf( | |
| parameters.isDexRuntime(), | |
| testBuilder -> | |
| testBuilder.addOptionsModification( | |
| options -> | |
| options | |
| .getOpenClosedInterfacesOptions() | |
| .suppressAllOpenInterfacesDueToMissingClasses())) | |
| .setMinApi(parameters.getApiLevel()) | |
| .addDataEntryResources( | |
| DataEntryResource.fromBytes( | |
| StringUtils.lines(MyScriptEngine1FactoryImpl.class.getTypeName()).getBytes(), | |
| "META-INF/services/" + ScriptEngineFactory.class.getTypeName(), | |
| Origin.unknown())) | |
| .addDataEntryResources( | |
| DataEntryResource.fromBytes( | |
| StringUtils.lines(MyScriptEngine2FactoryImpl.class.getTypeName()).getBytes(), | |
| "META-INF/services/" + ScriptEngineFactory.class.getTypeName(), | |
| Origin.unknown())) | |
| .apply( | |
| b -> { | |
| if (parameters.isDexRuntime()) { | |
| addRhinoForAndroid(b); | |
| b.allowDiagnosticWarningMessages(); | |
| } | |
| }) | |
| // TODO(b/136633154): This should work both with and without -dontobfuscate. | |
| .noMinification() | |
| // TODO(b/136633154): This should work both with and without -dontshrink. | |
| .noTreeShaking() | |
| .compile() | |
| .applyIf( | |
| parameters.isDexRuntime(), | |
| result -> | |
| result.assertAllWarningMessagesMatch( | |
| anyOf( | |
| containsString("Missing class "), | |
| containsString( | |
| "it is required for default or static interface methods desugaring"), | |
| equalTo("Resource 'META-INF/MANIFEST.MF' already exists.")))) | |
| .writeToZip(path) | |
| .run(parameters.getRuntime(), TestClass.class) | |
| // TODO(b/136633154): This should provide 2 script engines on both runtimes. The use of | |
| // the rhino-android library on Android will add the Rhino script engine, and the JVM | |
| // comes with "Oracle Nashorn" included. | |
| .assertSuccessWithOutput( | |
| parameters.isCfRuntime() | |
| ? StringUtils.lines("MyEngine1", "MyEngine2", "Oracle Nashorn") | |
| : StringUtils.lines("Mozilla Rhino", "MyEngine1", "MyEngine2")); | |
| // TODO(b/136633154): On the JVM this should always be there as the service loading is in | |
| // the library. On Android we should be able to rewrite the code and not have it. | |
| // Check that we still have META-INF/services/javax.script.ScriptEngineFactory. | |
| ZipFile zip = new ZipFile(path.toFile()); | |
| ZipEntry entry = zip.getEntry("META-INF/services/" + ScriptEngineFactory.class.getTypeName()); | |
| assertNotNull(entry); | |
| assertEquals( | |
| // For dex this also contains Rhino: com.sun.script.javascript.RhinoScriptEngineFactory. | |
| parameters.isCfRuntime() ? 2 : 3, | |
| StringUtils.splitLines( | |
| new String(StreamUtils.StreamToByteArrayClose(zip.getInputStream(entry)))) | |
| .size()); | |
| } | |
| static class TestClass { | |
| public static void main(String[] args) { | |
| List<String> factoryNames = new ArrayList<>(); | |
| for (ScriptEngineFactory factory : new ScriptEngineManager().getEngineFactories()) { | |
| factoryNames.add(factory.getEngineName()); | |
| } | |
| Collections.sort(factoryNames); | |
| for (String name : factoryNames) { | |
| System.out.println(name); | |
| } | |
| } | |
| } | |
| public static class MyScriptEngineFactoryBase implements ScriptEngineFactory { | |
| public final String variant; | |
| public MyScriptEngineFactoryBase(String variant) { | |
| this.variant = variant; | |
| } | |
| @Override | |
| public String getEngineName() { | |
| return "MyEngine" + variant; | |
| } | |
| @Override | |
| public String getEngineVersion() { | |
| return "0.1"; | |
| } | |
| @Override | |
| public List<String> getExtensions() { | |
| return Collections.emptyList(); | |
| } | |
| @Override | |
| public List<String> getMimeTypes() { | |
| List<String> result = new ArrayList<>(); | |
| result.add("text/my-script-" + variant); | |
| return result; | |
| } | |
| @Override | |
| public List<String> getNames() { | |
| List<String> result = new ArrayList<>(); | |
| result.add(getEngineName()); | |
| return result; | |
| } | |
| @Override | |
| public String getLanguageName() { | |
| return "MyLanguage" + variant; | |
| } | |
| @Override | |
| public String getLanguageVersion() { | |
| return "0.1"; | |
| } | |
| @Override | |
| public Object getParameter(String key) { | |
| return null; | |
| } | |
| @Override | |
| public String getMethodCallSyntax(String obj, String m, String... args) { | |
| return null; | |
| } | |
| @Override | |
| public String getOutputStatement(String toDisplay) { | |
| return null; | |
| } | |
| @Override | |
| public String getProgram(String... statements) { | |
| return null; | |
| } | |
| @Override | |
| public ScriptEngine getScriptEngine() { | |
| return new MyScriptEngine(variant); | |
| } | |
| } | |
| public static class MyScriptEngine1FactoryImpl extends MyScriptEngineFactoryBase { | |
| public MyScriptEngine1FactoryImpl() { | |
| super("1"); | |
| } | |
| } | |
| public static class MyScriptEngine2FactoryImpl extends MyScriptEngineFactoryBase { | |
| public MyScriptEngine2FactoryImpl() { | |
| super("2"); | |
| } | |
| } | |
| public static class MyScriptEngine implements ScriptEngine { | |
| public final String variant; | |
| public MyScriptEngine(String variant) { | |
| this.variant = variant; | |
| } | |
| @Override | |
| public Object eval(String script, ScriptContext context) throws ScriptException { | |
| throw new ScriptException("Not implemented"); | |
| } | |
| @Override | |
| public Object eval(Reader reader, ScriptContext context) throws ScriptException { | |
| throw new ScriptException("Not implemented"); | |
| } | |
| @Override | |
| public Object eval(String script) throws ScriptException { | |
| return "Engine " + variant + " evaluation of: " + script; | |
| } | |
| @Override | |
| public Object eval(Reader reader) throws ScriptException { | |
| throw new ScriptException("Not implemented"); | |
| } | |
| @Override | |
| public Object eval(String script, Bindings n) throws ScriptException { | |
| throw new ScriptException("Not implemented"); | |
| } | |
| @Override | |
| public Object eval(Reader reader, Bindings n) throws ScriptException { | |
| throw new ScriptException("Not implemented"); | |
| } | |
| @Override | |
| public void put(String key, Object value) {} | |
| @Override | |
| public Object get(String key) { | |
| return null; | |
| } | |
| @Override | |
| public Bindings getBindings(int scope) { | |
| return null; | |
| } | |
| @Override | |
| public void setBindings(Bindings bindings, int scope) {} | |
| @Override | |
| public Bindings createBindings() { | |
| return null; | |
| } | |
| @Override | |
| public ScriptContext getContext() { | |
| return null; | |
| } | |
| @Override | |
| public void setContext(ScriptContext context) {} | |
| @Override | |
| public ScriptEngineFactory getFactory() { | |
| return null; | |
| } | |
| } | |
| } |