Browse Source

[Painless] Removes extraneous compile method (#35323)

With the removal of SearchScript (#34730), an extraneous compile method 
was left behind in PainlessScriptEngine. This change removes the method and 
updates the tests that depend on it to use the main compile method which 
gives better test coverage.
Jack Conradson 7 years ago
parent
commit
efe9beca7b

+ 0 - 37
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java

@@ -33,7 +33,6 @@ import org.objectweb.asm.Type;
 import org.objectweb.asm.commons.GeneratorAdapter;
 
 import java.lang.invoke.MethodType;
-import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.security.AccessControlContext;
 import java.security.AccessController;
@@ -333,42 +332,6 @@ public final class PainlessScriptEngine implements ScriptEngine {
         }
     }
 
-    Constructor<?> compile(Compiler compiler, String scriptName, String source, Map<String, String> params) {
-        final CompilerSettings compilerSettings = buildCompilerSettings(params);
-
-        // Check we ourselves are not being called by unprivileged code.
-        SpecialPermission.check();
-
-        // Create our loader (which loads compiled code with no permissions).
-        final Loader loader = AccessController.doPrivileged(new PrivilegedAction<Loader>() {
-            @Override
-            public Loader run() {
-                return compiler.createLoader(getClass().getClassLoader());
-            }
-        });
-
-        try {
-            // Drop all permissions to actually compile the code itself.
-            return AccessController.doPrivileged(new PrivilegedAction<Constructor<?>>() {
-                @Override
-                public Constructor<?> run() {
-                    String name = scriptName == null ? source : scriptName;
-                    Constructor<?> constructor = compiler.compile(loader, new MainMethodReserved(), name, source, compilerSettings);
-
-                    try {
-                        return constructor;
-                    } catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
-                        throw new IllegalStateException(
-                            "An internal error occurred attempting to define the script [" + name + "].", exception);
-                    }
-                }
-            }, COMPILATION_CONTEXT);
-        // Note that it is safe to catch any of the following errors since Painless is stateless.
-        } catch (OutOfMemoryError | StackOverflowError | VerifyError | Exception e) {
-            throw convertToScriptException(source, e);
-        }
-    }
-
     void compile(Compiler compiler, Loader loader, MainMethodReserved reserved,
                  String scriptName, String source, Map<String, String> params) {
         final CompilerSettings compilerSettings = buildCompilerSettings(params);

+ 349 - 167
modules/lang-painless/src/test/java/org/elasticsearch/painless/BaseClassTests.java

@@ -19,16 +19,15 @@
 
 package org.elasticsearch.painless;
 
-import org.elasticsearch.painless.lookup.PainlessLookup;
-import org.elasticsearch.painless.lookup.PainlessLookupBuilder;
 import org.elasticsearch.painless.spi.Whitelist;
+import org.elasticsearch.script.ScriptContext;
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import static java.util.Collections.emptyMap;
-import static java.util.Collections.singletonMap;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.startsWith;
@@ -38,10 +37,40 @@ import static org.hamcrest.Matchers.startsWith;
  */
 public class BaseClassTests extends ScriptTestCase {
 
-    private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(Whitelist.BASE_WHITELISTS);
+    protected Map<ScriptContext<?>, List<Whitelist>> scriptContexts() {
+        Map<ScriptContext<?>, List<Whitelist>> contexts = new HashMap<>();
+        contexts.put(Gets.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(NoArgs.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(OneArg.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ArrayArg.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(PrimitiveArrayArg.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(DefArrayArg.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ManyArgs.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(VarArgs.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(DefaultMethods.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ReturnsVoid.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ReturnsPrimitiveBoolean.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ReturnsPrimitiveInt.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ReturnsPrimitiveFloat.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(ReturnsPrimitiveDouble.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(NoArgsConstant.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(WrongArgsConstant.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(WrongLengthOfArgConstant.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(UnknownArgType.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(UnknownReturnType.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(UnknownArgTypeInArray.CONTEXT, Whitelist.BASE_WHITELISTS);
+        contexts.put(TwoExecuteMethods.CONTEXT, Whitelist.BASE_WHITELISTS);
+        return contexts;
+    }
 
     public abstract static class Gets {
 
+        public interface Factory {
+            Gets newInstance(String testString, int testInt, Map<String, Object> params);
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("gets", Factory.class);
+
         private final String testString;
         private final int testInt;
         private final Map<String, Object> testMap;
@@ -58,115 +87,137 @@ public class BaseClassTests extends ScriptTestCase {
         public String getTestString() {
             return testString;
         }
-
         public int getTestInt() {
             return Math.abs(testInt);
         }
-
         public Map<String, Object> getTestMap() {
             return testMap == null ? new HashMap<>() : testMap;
         }
     }
-
     public void testGets() throws Exception {
-        Compiler compiler = new Compiler(Gets.class, null, null, painlessLookup);
         Map<String, Object> map = new HashMap<>();
         map.put("s", 1);
 
-        assertEquals(1, ((Gets)scriptEngine.compile(compiler, null, "testInt", emptyMap()).newInstance("s", -1, null)).execute());
+        assertEquals(1, scriptEngine.compile("testGets0", "testInt", Gets.CONTEXT, emptyMap()).newInstance("s", -1, null).execute());
         assertEquals(Collections.emptyMap(),
-                ((Gets)scriptEngine.compile(compiler, null, "testMap", emptyMap()).newInstance("s", -1, null)).execute());
-        assertEquals(Collections.singletonMap("1", "1"), ((Gets)scriptEngine.compile(
-                compiler, null, "testMap", emptyMap()).newInstance("s", -1, Collections.singletonMap("1", "1"))).execute());
-        assertEquals("s", ((Gets)scriptEngine.compile(compiler, null, "testString", emptyMap()).newInstance("s", -1, null)).execute());
-        assertEquals(map, ((Gets)scriptEngine.compile(
-                compiler, null, "testMap.put(testString, testInt); testMap", emptyMap()).newInstance("s", -1, null)).execute());
+                scriptEngine.compile("testGets1", "testMap", Gets.CONTEXT, emptyMap()).newInstance("s", -1, null).execute());
+        assertEquals(Collections.singletonMap("1", "1"),
+                scriptEngine.compile("testGets2", "testMap", Gets.CONTEXT, emptyMap())
+                        .newInstance("s", -1, Collections.singletonMap("1", "1")).execute());
+        assertEquals("s", scriptEngine.compile("testGets3", "testString", Gets.CONTEXT, emptyMap()).newInstance("s", -1, null).execute());
+        assertEquals(map,
+                scriptEngine.compile("testGets4", "testMap.put(testString, testInt); testMap", Gets.CONTEXT, emptyMap())
+                        .newInstance("s", -1, null).execute());
     }
 
     public abstract static class NoArgs {
+        public interface Factory {
+            NoArgs newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("noargs", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {};
         public abstract Object execute();
     }
     public void testNoArgs() throws Exception {
-        Compiler compiler = new Compiler(NoArgs.class, null, null, painlessLookup);
-        assertEquals(1, ((NoArgs)scriptEngine.compile(compiler, null, "1", emptyMap()).newInstance()).execute());
-        assertEquals("foo", ((NoArgs)scriptEngine.compile(compiler, null, "'foo'", emptyMap()).newInstance()).execute());
+        assertEquals(1, scriptEngine.compile("testNoArgs0", "1", NoArgs.CONTEXT, emptyMap()).newInstance().execute());
+        assertEquals("foo", scriptEngine.compile("testNoArgs1", "'foo'", NoArgs.CONTEXT, emptyMap()).newInstance().execute());
 
         Exception e = expectScriptThrows(IllegalArgumentException.class, () ->
-                scriptEngine.compile(compiler, null, "doc", emptyMap()));
+                scriptEngine.compile("testNoArgs2", "doc", NoArgs.CONTEXT, emptyMap()));
         assertEquals("Variable [doc] is not defined.", e.getMessage());
-        // _score was once embedded into painless by deep magic
         e = expectScriptThrows(IllegalArgumentException.class, () ->
-                scriptEngine.compile(compiler, null, "_score", emptyMap()));
+                scriptEngine.compile("testNoArgs3", "_score", NoArgs.CONTEXT, emptyMap()));
         assertEquals("Variable [_score] is not defined.", e.getMessage());
 
         String debug = Debugger.toString(NoArgs.class, "int i = 0", new CompilerSettings());
-        /* Elasticsearch requires that scripts that return nothing return null. We hack that together by returning null from scripts that
-         * return Object if they don't return anything. */
         assertThat(debug, containsString("ACONST_NULL"));
         assertThat(debug, containsString("ARETURN"));
     }
 
     public abstract static class OneArg {
+        public interface Factory {
+            OneArg newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("onearg", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"arg"};
         public abstract Object execute(Object arg);
     }
     public void testOneArg() throws Exception {
-        Compiler compiler = new Compiler(OneArg.class, null, null, painlessLookup);
         Object rando = randomInt();
-        assertEquals(rando, ((OneArg)scriptEngine.compile(compiler, null, "arg", emptyMap()).newInstance()).execute(rando));
+        assertEquals(rando, scriptEngine.compile("testOneArg0", "arg", OneArg.CONTEXT, emptyMap()).newInstance().execute(rando));
         rando = randomAlphaOfLength(5);
-        assertEquals(rando, ((OneArg)scriptEngine.compile(compiler, null, "arg", emptyMap()).newInstance()).execute(rando));
-
-        Compiler noargs = new Compiler(NoArgs.class, null, null, painlessLookup);
-        Exception e = expectScriptThrows(IllegalArgumentException.class, () ->
-                scriptEngine.compile(noargs, null, "doc", emptyMap()));
-        assertEquals("Variable [doc] is not defined.", e.getMessage());
-        // _score was once embedded into painless by deep magic
-        e = expectScriptThrows(IllegalArgumentException.class, () ->
-                scriptEngine.compile(noargs, null, "_score", emptyMap()));
-        assertEquals("Variable [_score] is not defined.", e.getMessage());
+        assertEquals(rando, scriptEngine.compile("testOneArg1", "arg", OneArg.CONTEXT, emptyMap()).newInstance().execute(rando));
     }
 
     public abstract static class ArrayArg {
+        public interface Factory {
+            ArrayArg newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("arrayarg", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"arg"};
         public abstract Object execute(String[] arg);
     }
     public void testArrayArg() throws Exception {
-        Compiler compiler = new Compiler(ArrayArg.class, null, null, painlessLookup);
         String rando = randomAlphaOfLength(5);
         assertEquals(rando,
-                ((ArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap()).newInstance()).execute(new String[] {rando, "foo"}));
+                scriptEngine.compile("testArrayArg0", "arg[0]", ArrayArg.CONTEXT, emptyMap())
+                        .newInstance().execute(new String[] {rando, "foo"}));
     }
 
     public abstract static class PrimitiveArrayArg {
+        public interface Factory {
+            PrimitiveArrayArg newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("primitivearrayarg", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"arg"};
         public abstract Object execute(int[] arg);
     }
     public void testPrimitiveArrayArg() throws Exception {
-        Compiler compiler = new Compiler(PrimitiveArrayArg.class, null, null, painlessLookup);
         int rando = randomInt();
-        assertEquals(rando, ((PrimitiveArrayArg)scriptEngine.compile(
-                compiler, null, "arg[0]", emptyMap()).newInstance()).execute(new int[] {rando, 10}));
+        assertEquals(rando,
+                scriptEngine.compile("PrimitiveArrayArg0", "arg[0]", PrimitiveArrayArg.CONTEXT, emptyMap())
+                        .newInstance().execute(new int[] {rando, 10}));
     }
 
     public abstract static class DefArrayArg {
+        public interface Factory {
+            DefArrayArg newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("defarrayarg", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"arg"};
         public abstract Object execute(Object[] arg);
     }
     public void testDefArrayArg()throws Exception {
-        Compiler compiler = new Compiler(DefArrayArg.class, null, null, painlessLookup);
         Object rando = randomInt();
         assertEquals(rando,
-                ((DefArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap()).newInstance()).execute(new Object[] {rando, 10}));
+                scriptEngine.compile("testDefArray0", "arg[0]", DefArrayArg.CONTEXT, emptyMap())
+                        .newInstance().execute(new Object[] {rando, 10}));
         rando = randomAlphaOfLength(5);
         assertEquals(rando,
-                ((DefArrayArg)scriptEngine.compile(compiler, null, "arg[0]", emptyMap()).newInstance()).execute(new Object[] {rando, 10}));
-        assertEquals(5, ((DefArrayArg)scriptEngine.compile(
-                compiler, null, "arg[0].length()", emptyMap()).newInstance()).execute(new Object[] {rando, 10}));
+                scriptEngine.compile("testDefArray1", "arg[0]", DefArrayArg.CONTEXT, emptyMap())
+                        .newInstance().execute(new Object[] {rando, 10}));
+        assertEquals(5, scriptEngine.compile(
+                "testDefArray2", "arg[0].length()", DefArrayArg.CONTEXT, emptyMap())
+                .newInstance().execute(new Object[] {rando, 10}));
     }
 
     public abstract static class ManyArgs {
+        public interface Factory {
+            ManyArgs newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("manyargs", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"a", "b", "c", "d"};
         public abstract Object execute(int a, int b, int c, int d);
         public abstract boolean needsA();
@@ -175,41 +226,53 @@ public class BaseClassTests extends ScriptTestCase {
         public abstract boolean needsD();
     }
     public void testManyArgs() throws Exception {
-        Compiler compiler = new Compiler(ManyArgs.class, null, null, painlessLookup);
         int rando = randomInt();
-        assertEquals(rando, ((ManyArgs)scriptEngine.compile(compiler, null, "a", emptyMap()).newInstance()).execute(rando, 0, 0, 0));
-        assertEquals(10, ((ManyArgs)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap()).newInstance()).execute(1, 2, 3, 4));
+        assertEquals(rando,
+                scriptEngine.compile("testManyArgs0", "a", ManyArgs.CONTEXT, emptyMap()).newInstance().execute(rando, 0, 0, 0));
+        assertEquals(10,
+                scriptEngine.compile("testManyArgs1", "a + b + c + d", ManyArgs.CONTEXT, emptyMap()).newInstance().execute(1, 2, 3, 4));
 
         // While we're here we can verify that painless correctly finds used variables
-        ManyArgs script = (ManyArgs)scriptEngine.compile(compiler, null, "a", emptyMap()).newInstance();
+        ManyArgs script = scriptEngine.compile("testManyArgs2", "a", ManyArgs.CONTEXT, emptyMap()).newInstance();
         assertTrue(script.needsA());
         assertFalse(script.needsB());
         assertFalse(script.needsC());
         assertFalse(script.needsD());
-        script = (ManyArgs)scriptEngine.compile(compiler, null, "a + b + c", emptyMap()).newInstance();
+        script = scriptEngine.compile("testManyArgs3", "a + b + c", ManyArgs.CONTEXT, emptyMap()).newInstance();
         assertTrue(script.needsA());
         assertTrue(script.needsB());
         assertTrue(script.needsC());
         assertFalse(script.needsD());
-        script = (ManyArgs)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap()).newInstance();
+        script = scriptEngine.compile("testManyArgs4", "a + b + c + d", ManyArgs.CONTEXT, emptyMap()).newInstance();
         assertTrue(script.needsA());
         assertTrue(script.needsB());
         assertTrue(script.needsC());
         assertTrue(script.needsD());
     }
 
-    public abstract static class VarargTest {
+    public abstract static class VarArgs {
+        public interface Factory {
+            VarArgs newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("varargs", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"arg"};
         public abstract Object execute(String... arg);
     }
-    public void testVararg() throws Exception {
-        Compiler compiler = new Compiler(VarargTest.class, null, null, painlessLookup);
+    public void testVarArgs() throws Exception {
         assertEquals("foo bar baz",
-                ((VarargTest)scriptEngine.compile(compiler, null, "String.join(' ', Arrays.asList(arg))", emptyMap()).newInstance())
-                        .execute("foo", "bar", "baz"));
+                scriptEngine.compile("testVarArgs0", "String.join(' ', Arrays.asList(arg))", VarArgs.CONTEXT, emptyMap())
+                        .newInstance().execute("foo", "bar", "baz"));
     }
 
     public abstract static class DefaultMethods {
+        public interface Factory {
+            DefaultMethods newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("defaultmethods", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"a", "b", "c", "d"};
         public abstract Object execute(int a, int b, int c, int d);
         public Object executeWithOne() {
@@ -220,28 +283,38 @@ public class BaseClassTests extends ScriptTestCase {
         }
     }
     public void testDefaultMethods() throws Exception {
-        Compiler compiler = new Compiler(DefaultMethods.class, null, null, painlessLookup);
         int rando = randomInt();
-        assertEquals(rando, ((DefaultMethods)scriptEngine.compile(compiler, null, "a", emptyMap()).newInstance()).execute(rando, 0, 0, 0));
         assertEquals(rando,
-                ((DefaultMethods)scriptEngine.compile(compiler, null, "a", emptyMap()).newInstance()).executeWithASingleOne(rando, 0, 0));
+                scriptEngine.compile("testDefaultMethods0", "a", DefaultMethods.CONTEXT, emptyMap()).newInstance().execute(rando, 0, 0, 0));
+        assertEquals(rando,
+                scriptEngine.compile("testDefaultMethods1", "a", DefaultMethods.CONTEXT, emptyMap())
+                        .newInstance().executeWithASingleOne(rando, 0, 0));
         assertEquals(10,
-                ((DefaultMethods)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap()).newInstance()).execute(1, 2, 3, 4));
-        assertEquals(4, ((DefaultMethods)scriptEngine.compile(compiler, null, "a + b + c + d", emptyMap()).newInstance()).executeWithOne());
-        assertEquals(7, ((DefaultMethods)scriptEngine.compile(
-                compiler, null, "a + b + c + d", emptyMap()).newInstance()).executeWithASingleOne(1, 2, 3));
+                scriptEngine.compile("testDefaultMethods2", "a + b + c + d", DefaultMethods.CONTEXT, emptyMap())
+                        .newInstance().execute(1, 2, 3, 4));
+        assertEquals(4,
+                scriptEngine.compile("testDefaultMethods3", "a + b + c + d", DefaultMethods.CONTEXT, emptyMap())
+                        .newInstance().executeWithOne());
+        assertEquals(7,
+                scriptEngine.compile("testDefaultMethods4", "a + b + c + d", DefaultMethods.CONTEXT, emptyMap())
+                        .newInstance().executeWithASingleOne(1, 2, 3));
     }
 
     public abstract static class ReturnsVoid {
+        public interface Factory {
+            ReturnsVoid newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("returnsvoid", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"map"};
         public abstract void execute(Map<String, Object> map);
     }
     public void testReturnsVoid() throws Exception {
-        Compiler compiler = new Compiler(ReturnsVoid.class, null, null, painlessLookup);
         Map<String, Object> map = new HashMap<>();
-        ((ReturnsVoid)scriptEngine.compile(compiler, null, "map.a = 'foo'", emptyMap()).newInstance()).execute(map);
-        assertEquals(singletonMap("a", "foo"), map);
-        ((ReturnsVoid)scriptEngine.compile(compiler, null, "map.remove('a')", emptyMap()).newInstance()).execute(map);
+        scriptEngine.compile("testReturnsVoid0", "map.a = 'foo'", ReturnsVoid.CONTEXT, emptyMap()).newInstance().execute(map);
+        assertEquals(Collections.singletonMap("a", "foo"), map);
+        scriptEngine.compile("testReturnsVoid1", "map.remove('a')", ReturnsVoid.CONTEXT, emptyMap()).newInstance().execute(map);
         assertEquals(emptyMap(), map);
 
         String debug = Debugger.toString(ReturnsVoid.class, "int i = 0", new CompilerSettings());
@@ -252,26 +325,38 @@ public class BaseClassTests extends ScriptTestCase {
     }
 
     public abstract static class ReturnsPrimitiveBoolean {
+        public interface Factory {
+            ReturnsPrimitiveBoolean newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("returnsprimitiveboolean", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {};
         public abstract boolean execute();
     }
     public void testReturnsPrimitiveBoolean() throws Exception {
-        Compiler compiler = new Compiler(ReturnsPrimitiveBoolean.class, null, null, painlessLookup);
-
-        assertEquals(true, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "true", emptyMap()).newInstance()).execute());
-        assertEquals(false, ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "false", emptyMap()).newInstance()).execute());
-        assertEquals(true,
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "Boolean.TRUE", emptyMap()).newInstance()).execute());
-        assertEquals(false,
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "Boolean.FALSE", emptyMap()).newInstance()).execute());
-
-        assertEquals(true,
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "def i = true; i", emptyMap()).newInstance()).execute());
-        assertEquals(true, ((ReturnsPrimitiveBoolean)scriptEngine.compile(
-                compiler, null, "def i = Boolean.TRUE; i", emptyMap()).newInstance()).execute());
-
-        assertEquals(true,
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "true || false", emptyMap()).newInstance()).execute());
+        assertTrue(
+                scriptEngine.compile("testReturnsPrimitiveBoolean0", "true", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertFalse(
+                scriptEngine.compile("testReturnsPrimitiveBoolean1", "false", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertTrue(
+                scriptEngine.compile("testReturnsPrimitiveBoolean2", "Boolean.TRUE", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertFalse(
+                scriptEngine.compile("testReturnsPrimitiveBoolean3", "Boolean.FALSE", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
+
+        assertTrue(
+                scriptEngine.compile("testReturnsPrimitiveBoolean4", "def i = true; i", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertTrue(
+                scriptEngine.compile("testReturnsPrimitiveBoolean5", "def i = Boolean.TRUE; i", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertTrue(
+                scriptEngine.compile("testReturnsPrimitiveBoolean6", "true || false", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
 
         String debug = Debugger.toString(ReturnsPrimitiveBoolean.class, "false", new CompilerSettings());
         assertThat(debug, containsString("ICONST_0"));
@@ -279,44 +364,67 @@ public class BaseClassTests extends ScriptTestCase {
         assertThat(debug, containsString("IRETURN"));
 
         Exception e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "1L", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveBoolean7", "1L",ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
         assertEquals("Cannot cast from [long] to [boolean].", e.getMessage());
         e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "1.1f", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveBoolean8", "1.1f", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
         assertEquals("Cannot cast from [float] to [boolean].", e.getMessage());
         e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "1.1d", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveBoolean9", "1.1d", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
         assertEquals("Cannot cast from [double] to [boolean].", e.getMessage());
         expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "def i = 1L; i", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveBoolean10", "def i = 1L; i", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
         expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "def i = 1.1f; i", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveBoolean11", "def i = 1.1f; i", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
         expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "def i = 1.1d; i", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveBoolean12", "def i = 1.1d; i", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
 
-        assertEquals(false,
-                ((ReturnsPrimitiveBoolean)scriptEngine.compile(compiler, null, "int i = 0", emptyMap()).newInstance()).execute());
+        assertFalse(
+                scriptEngine.compile("testReturnsPrimitiveBoolean13", "int i = 0", ReturnsPrimitiveBoolean.CONTEXT, emptyMap())
+                        .newInstance().execute());
     }
 
     public abstract static class ReturnsPrimitiveInt {
+        public interface Factory {
+            ReturnsPrimitiveInt newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("returnsprimitiveint", Factory.class);
+        
         public static final String[] PARAMETERS = new String[] {};
         public abstract int execute();
     }
     public void testReturnsPrimitiveInt() throws Exception {
-        Compiler compiler = new Compiler(ReturnsPrimitiveInt.class, null, null, painlessLookup);
-
-        assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1", emptyMap()).newInstance()).execute());
-        assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "(int) 1L", emptyMap()).newInstance()).execute());
-        assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "(int) 1.1d", emptyMap()).newInstance()).execute());
-        assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "(int) 1.1f", emptyMap()).newInstance()).execute());
+        assertEquals(1, 
+                scriptEngine.compile("testReturnsPrimitiveInt0", "1", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertEquals(1, 
+                scriptEngine.compile("testReturnsPrimitiveInt1", "(int) 1L", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertEquals(1, scriptEngine.compile("testReturnsPrimitiveInt2", "(int) 1.1d", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                .newInstance().execute());
         assertEquals(1,
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "Integer.valueOf(1)", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt3", "(int) 1.1f", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertEquals(1,
+                scriptEngine.compile("testReturnsPrimitiveInt4", "Integer.valueOf(1)", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
 
-        assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "def i = 1; i", emptyMap()).newInstance()).execute());
-        assertEquals(1, ((ReturnsPrimitiveInt)scriptEngine.compile(
-                compiler, null, "def i = Integer.valueOf(1); i", emptyMap()).newInstance()).execute());
+        assertEquals(1,
+                scriptEngine.compile("testReturnsPrimitiveInt5", "def i = 1; i", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        assertEquals(1,
+                scriptEngine.compile("testReturnsPrimitiveInt6", "def i = Integer.valueOf(1); i", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
 
-        assertEquals(2, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1 + 1", emptyMap()).newInstance()).execute());
+        assertEquals(2,
+                scriptEngine.compile("testReturnsPrimitiveInt7", "1 + 1", ReturnsPrimitiveInt.CONTEXT, emptyMap()).newInstance().execute());
 
         String debug = Debugger.toString(ReturnsPrimitiveInt.class, "1", new CompilerSettings());
         assertThat(debug, containsString("ICONST_1"));
@@ -324,49 +432,66 @@ public class BaseClassTests extends ScriptTestCase {
         assertThat(debug, containsString("IRETURN"));
 
         Exception e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1L", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt8", "1L", ReturnsPrimitiveInt.CONTEXT, emptyMap()).newInstance().execute());
         assertEquals("Cannot cast from [long] to [int].", e.getMessage());
         e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1.1f", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt9", "1.1f", ReturnsPrimitiveInt.CONTEXT, emptyMap()).newInstance().execute());
         assertEquals("Cannot cast from [float] to [int].", e.getMessage());
         e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "1.1d", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt10", "1.1d", ReturnsPrimitiveInt.CONTEXT, emptyMap()).newInstance().execute());
         assertEquals("Cannot cast from [double] to [int].", e.getMessage());
         expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "def i = 1L; i", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt11", "def i = 1L; i", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
         expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "def i = 1.1f; i", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt12", "def i = 1.1f; i", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
         expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "def i = 1.1d; i", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveInt13", "def i = 1.1d; i", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                        .newInstance().execute());
 
-        assertEquals(0, ((ReturnsPrimitiveInt)scriptEngine.compile(compiler, null, "int i = 0", emptyMap()).newInstance()).execute());
+        assertEquals(0, scriptEngine.compile("testReturnsPrimitiveInt14", "int i = 0", ReturnsPrimitiveInt.CONTEXT, emptyMap())
+                .newInstance().execute());
     }
 
     public abstract static class ReturnsPrimitiveFloat {
+        public interface Factory {
+            ReturnsPrimitiveFloat newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("returnsprimitivefloat", Factory.class);
+        
         public static final String[] PARAMETERS = new String[] {};
         public abstract float execute();
     }
     public void testReturnsPrimitiveFloat() throws Exception {
-        Compiler compiler = new Compiler(ReturnsPrimitiveFloat.class, null, null, painlessLookup);
-
-        assertEquals(1.1f, ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "1.1f", emptyMap()).newInstance()).execute(), 0);
         assertEquals(1.1f,
-                ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "(float) 1.1d", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveFloat0", "1.1f", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
+        assertEquals(1.1f,
+                scriptEngine.compile("testReturnsPrimitiveFloat1", "(float) 1.1d", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
         assertEquals(1.1f,
-                ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "def d = 1.1f; d", emptyMap()).newInstance()).execute(), 0);
-        assertEquals(1.1f, ((ReturnsPrimitiveFloat)scriptEngine.compile(
-                compiler, null, "def d = Float.valueOf(1.1f); d", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveFloat2", "def d = 1.1f; d", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
+        assertEquals(1.1f, scriptEngine.compile(
+                "testReturnsPrimitiveFloat3", "def d = Float.valueOf(1.1f); d", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                .newInstance().execute(), 0);
 
         assertEquals(1.1f + 6.7f,
-                ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "1.1f + 6.7f", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveFloat4", "1.1f + 6.7f", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
 
         Exception e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "1.1d", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveFloat5", "1.1d", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute());
         assertEquals("Cannot cast from [double] to [float].", e.getMessage());
         e = expectScriptThrows(ClassCastException.class, () ->
-                ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "def d = 1.1d; d", emptyMap()).newInstance()).execute());
-        e = expectScriptThrows(ClassCastException.class, () -> ((ReturnsPrimitiveFloat)scriptEngine.compile(
-                compiler, null, "def d = Double.valueOf(1.1); d", emptyMap()).newInstance()).execute());
+                scriptEngine.compile("testReturnsPrimitiveFloat6", "def d = 1.1d; d", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute());
+        e = expectScriptThrows(ClassCastException.class, () -> scriptEngine.compile(
+                "testReturnsPrimitiveFloat7", "def d = Double.valueOf(1.1); d", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                .newInstance().execute());
 
         String debug = Debugger.toString(ReturnsPrimitiveFloat.class, "1f", new CompilerSettings());
         assertThat(debug, containsString("FCONST_1"));
@@ -374,41 +499,62 @@ public class BaseClassTests extends ScriptTestCase {
         assertThat(debug, containsString("FRETURN"));
 
         assertEquals(0.0f,
-                ((ReturnsPrimitiveFloat)scriptEngine.compile(compiler, null, "int i = 0", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveFloat8", "int i = 0", ReturnsPrimitiveFloat.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
     }
 
-    public abstract static class ReturnsPrimitiveDouble {
+   public abstract static class ReturnsPrimitiveDouble {
+       public interface Factory {
+           ReturnsPrimitiveDouble newInstance();
+       }
+
+       public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("returnsprimitivedouble", Factory.class);
+        
         public static final String[] PARAMETERS = new String[] {};
         public abstract double execute();
     }
     public void testReturnsPrimitiveDouble() throws Exception {
-        Compiler compiler = new Compiler(ReturnsPrimitiveDouble.class, null, null, painlessLookup);
-
-        assertEquals(1.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1", emptyMap()).newInstance()).execute(), 0);
-        assertEquals(1.0, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1L", emptyMap()).newInstance()).execute(), 0);
-        assertEquals(1.1, ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1.1d", emptyMap()).newInstance()).execute(), 0);
+        assertEquals(1.0,
+                scriptEngine.compile("testReturnsPrimitiveDouble0", "1", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
+        assertEquals(1.0,
+                scriptEngine.compile("testReturnsPrimitiveDouble1", "1L", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
+        assertEquals(1.1,
+                scriptEngine.compile("testReturnsPrimitiveDouble2", "1.1d", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
         assertEquals((double) 1.1f,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1.1f", emptyMap()).newInstance()).execute(), 0);
-        assertEquals(1.1, ((ReturnsPrimitiveDouble)scriptEngine.compile(
-                compiler, null, "Double.valueOf(1.1)", emptyMap()).newInstance()).execute(), 0);
-        assertEquals((double) 1.1f, ((ReturnsPrimitiveDouble)scriptEngine.compile(
-                compiler, null, "Float.valueOf(1.1f)", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble3", "1.1f", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
+        assertEquals(1.1, scriptEngine.compile(
+                "testReturnsPrimitiveDouble4", "Double.valueOf(1.1)", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                .newInstance().execute(), 0);
+        assertEquals((double) 1.1f, scriptEngine.compile(
+                "testReturnsPrimitiveDouble5", "Float.valueOf(1.1f)", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                .newInstance().execute(), 0);
 
         assertEquals(1.0,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "def d = 1; d", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble6", "def d = 1; d", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
         assertEquals(1.0,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "def d = 1L; d", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble7", "def d = 1L; d", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
         assertEquals(1.1,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "def d = 1.1d; d", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble8", "def d = 1.1d; d", ReturnsPrimitiveDouble.CONTEXT, emptyMap()).
+                        newInstance().execute(), 0);
         assertEquals((double) 1.1f,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "def d = 1.1f; d", emptyMap()).newInstance()).execute(), 0);
-        assertEquals(1.1, ((ReturnsPrimitiveDouble)scriptEngine.compile(
-                compiler, null, "def d = Double.valueOf(1.1); d", emptyMap()).newInstance()).execute(), 0);
-        assertEquals((double) 1.1f, ((ReturnsPrimitiveDouble)scriptEngine.compile(
-                compiler, null, "def d = Float.valueOf(1.1f); d", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble9", "def d = 1.1f; d", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
+        assertEquals(1.1, scriptEngine.compile(
+                "testReturnsPrimitiveDouble10", "def d = Double.valueOf(1.1); d", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                .newInstance().execute(), 0);
+        assertEquals((double) 1.1f, scriptEngine.compile(
+                "testReturnsPrimitiveDouble11", "def d = Float.valueOf(1.1f); d", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                .newInstance().execute(), 0);
 
         assertEquals(1.1 + 6.7,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "1.1 + 6.7", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble12", "1.1 + 6.7", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
 
         String debug = Debugger.toString(ReturnsPrimitiveDouble.class, "1", new CompilerSettings());
         assertThat(debug, containsString("DCONST_1"));
@@ -416,90 +562,126 @@ public class BaseClassTests extends ScriptTestCase {
         assertThat(debug, containsString("DRETURN"));
 
         assertEquals(0.0,
-                ((ReturnsPrimitiveDouble)scriptEngine.compile(compiler, null, "int i = 0", emptyMap()).newInstance()).execute(), 0);
+                scriptEngine.compile("testReturnsPrimitiveDouble13", "int i = 0", ReturnsPrimitiveDouble.CONTEXT, emptyMap())
+                        .newInstance().execute(), 0);
     }
 
-    public abstract static class NoArgumentsConstant {
+    public abstract static class NoArgsConstant {
+        public interface Factory {
+            NoArgsConstant newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("noargsconstant", Factory.class);
+
         public abstract Object execute(String foo);
     }
-    public void testNoArgumentsConstant() {
-        Compiler compiler = new Compiler(NoArgumentsConstant.class, null, null, painlessLookup);
+    public void testNoArgsConstant() {
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "1", emptyMap()));
+            scriptEngine.compile("testNoArgsConstant0", "1", NoArgsConstant.CONTEXT, emptyMap()).newInstance().execute("constant"));
         assertThat(e.getMessage(), startsWith(
                 "Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
-                + "names of the method arguments but [" + NoArgumentsConstant.class.getName() + "] doesn't have one."));
+                + "names of the method arguments but [" + NoArgsConstant.class.getName() + "] doesn't have one."));
     }
 
-    public abstract static class WrongArgumentsConstant {
+    public abstract static class WrongArgsConstant {
+        public interface Factory {
+            WrongArgsConstant newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("wrongargscontext", Factory.class);
+
         boolean[] PARAMETERS = new boolean[] {false};
         public abstract Object execute(String foo);
     }
-    public void testWrongArgumentsConstant() {
-        Compiler compiler = new Compiler(WrongArgumentsConstant.class, null, null, painlessLookup);
+    public void testWrongArgsConstant() {
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "1", emptyMap()));
+            scriptEngine.compile("testWrongArgsConstant0", "1", WrongArgsConstant.CONTEXT, emptyMap()));
         assertThat(e.getMessage(), startsWith(
                 "Painless needs a constant [String[] PARAMETERS] on all interfaces it implements with the "
-                + "names of the method arguments but [" + WrongArgumentsConstant.class.getName() + "] doesn't have one."));
+                + "names of the method arguments but [" + WrongArgsConstant.class.getName() + "] doesn't have one."));
     }
 
-    public abstract static class WrongLengthOfArgumentConstant {
+    public abstract static class WrongLengthOfArgConstant {
+        public interface Factory {
+            WrongLengthOfArgConstant newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("wronglengthofargcontext", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"foo", "bar"};
         public abstract Object execute(String foo);
     }
-    public void testWrongLengthOfArgumentConstant() {
-        Compiler compiler = new Compiler(WrongLengthOfArgumentConstant.class, null, null, painlessLookup);
+    public void testWrongLengthOfArgConstant() {
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "1", emptyMap()));
-        assertThat(e.getMessage(), startsWith("[" + WrongLengthOfArgumentConstant.class.getName() + "#ARGUMENTS] has length [2] but ["
-                + WrongLengthOfArgumentConstant.class.getName() + "#execute] takes [1] argument."));
+            scriptEngine.compile("testWrongLengthOfArgConstant", "1", WrongLengthOfArgConstant.CONTEXT, emptyMap()));
+        assertThat(e.getMessage(), startsWith("[" + WrongLengthOfArgConstant.class.getName() + "#ARGUMENTS] has length [2] but ["
+                + WrongLengthOfArgConstant.class.getName() + "#execute] takes [1] argument."));
     }
 
     public abstract static class UnknownArgType {
+        public interface Factory {
+            UnknownArgType newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("unknownargtype", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"foo"};
         public abstract Object execute(UnknownArgType foo);
     }
     public void testUnknownArgType() {
-        Compiler compiler = new Compiler(UnknownArgType.class, null, null, painlessLookup);
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "1", emptyMap()));
+            scriptEngine.compile("testUnknownArgType0", "1", UnknownArgType.CONTEXT, emptyMap()));
         assertEquals("[foo] is of unknown type [" + UnknownArgType.class.getName() + ". Painless interfaces can only accept arguments "
                 + "that are of whitelisted types.", e.getMessage());
     }
 
     public abstract static class UnknownReturnType {
+        public interface Factory {
+            UnknownReturnType newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("unknownreturntype", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"foo"};
         public abstract UnknownReturnType execute(String foo);
     }
     public void testUnknownReturnType() {
-        Compiler compiler = new Compiler(UnknownReturnType.class, null, null, painlessLookup);
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "1", emptyMap()));
+            scriptEngine.compile("testUnknownReturnType0", "1", UnknownReturnType.CONTEXT, emptyMap()));
         assertEquals("Painless can only implement execute methods returning a whitelisted type but [" + UnknownReturnType.class.getName()
                 + "#execute] returns [" + UnknownReturnType.class.getName() + "] which isn't whitelisted.", e.getMessage());
     }
 
     public abstract static class UnknownArgTypeInArray {
+        public interface Factory {
+            UnknownArgTypeInArray newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("unknownargtypeinarray", Factory.class);
+
         public static final String[] PARAMETERS = new String[] {"foo"};
         public abstract Object execute(UnknownArgTypeInArray[] foo);
     }
     public void testUnknownArgTypeInArray() {
-        Compiler compiler = new Compiler(UnknownArgTypeInArray.class, null, null, painlessLookup);
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "1", emptyMap()));
+            scriptEngine.compile("testUnknownAryTypeInArray0", "1", UnknownArgTypeInArray.CONTEXT, emptyMap()));
         assertEquals("[foo] is of unknown type [" + UnknownArgTypeInArray.class.getName() + ". Painless interfaces can only accept "
                 + "arguments that are of whitelisted types.", e.getMessage());
     }
 
     public abstract static class TwoExecuteMethods {
+        public interface Factory {
+            TwoExecuteMethods newInstance();
+        }
+
+        public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("twoexecutemethods", Factory.class);
+
         public abstract Object execute();
         public abstract Object execute(boolean foo);
     }
     public void testTwoExecuteMethods() {
-        Compiler compiler = new Compiler(TwoExecuteMethods.class, null, null, painlessLookup);
         Exception e = expectScriptThrows(IllegalArgumentException.class, false, () ->
-            scriptEngine.compile(compiler, null, "null", emptyMap()));
+            scriptEngine.compile("testTwoExecuteMethods0", "null", TwoExecuteMethods.CONTEXT, emptyMap()));
         assertEquals("Painless can only implement interfaces that have a single method named [execute] but ["
                 + TwoExecuteMethods.class.getName() + "] has more than one.", e.getMessage());
     }