/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless;
import junit.framework.AssertionFailedError;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.common.lucene.ScorerAware;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.painless.antlr.Walker;
import org.elasticsearch.script.CompiledScript;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptException;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import java.util.HashMap;
import java.util.Map;
import static org.hamcrest.Matchers.hasSize;
/**
* Base test case for scripting unit tests.
* <p>
* Typically just asserts the output of {@code exec()}
*/
public abstract class ScriptTestCase extends ESTestCase {
protected PainlessScriptEngine scriptEngine;
@Before
public void setup() {
scriptEngine = new PainlessScriptEngine(scriptEngineSettings());
}
/**
* Settings used to build the script engine. Override to customize settings like {@link RegexTests} does to enable regexes.
*/
protected Settings scriptEngineSettings() {
return Settings.EMPTY;
}
/** Compiles and returns the result of {@code script} */
public Object exec(String script) {
return exec(script, null, true);
}
/** Compiles and returns the result of {@code script} with access to {@code picky} */
public Object exec(String script, boolean picky) {
return exec(script, null, picky);
}
/** Compiles and returns the result of {@code script} with access to {@code vars} */
public Object exec(String script, Map<String, Object> vars, boolean picky) {
Map<String,String> compilerSettings = new HashMap<>();
compilerSettings.put(CompilerSettings.INITIAL_CALL_SITE_DEPTH, random().nextBoolean() ? "0" : "10");
return exec(script, vars, compilerSettings, null, picky);
}
/** Compiles and returns the result of {@code script} with access to {@code vars} and compile-time parameters */
public Object exec(String script, Map<String, Object> vars, Map<String,String> compileParams, Scorer scorer, boolean picky) {
// test for ambiguity errors before running the actual script if picky is true
if (picky) {
Definition definition = Definition.BUILTINS;
ScriptInterface scriptInterface = new ScriptInterface(definition, GenericElasticsearchScript.class);
CompilerSettings pickySettings = new CompilerSettings();
pickySettings.setPicky(true);
pickySettings.setRegexesEnabled(CompilerSettings.REGEX_ENABLED.get(scriptEngineSettings()));
Walker.buildPainlessTree(scriptInterface, getTestName(), script, pickySettings,
definition, null);
}
// test actual script execution
Object object = scriptEngine.compile(null, script, compileParams);
CompiledScript compiled = new CompiledScript(ScriptType.INLINE, getTestName(), "painless", object);
ExecutableScript executableScript = scriptEngine.executable(compiled, vars);
if (scorer != null) {
((ScorerAware)executableScript).setScorer(scorer);
}
return executableScript.run();
}
/**
* Uses the {@link Debugger} to get the bytecode output for a script and compare
* it against an expected bytecode passed in as a String.
*/
public void assertBytecodeExists(String script, String bytecode) {
final String asm = Debugger.toString(script);
assertTrue("bytecode not found, got: \n" + asm , asm.contains(bytecode));
}
/**
* Uses the {@link Debugger} to get the bytecode output for a script and compare
* it against an expected bytecode pattern as a regular expression (please try to avoid!)
*/
public void assertBytecodeHasPattern(String script, String pattern) {
final String asm = Debugger.toString(script);
assertTrue("bytecode not found, got: \n" + asm , asm.matches(pattern));
}
/** Checks a specific exception class is thrown (boxed inside ScriptException) and returns it. */
public static <T extends Throwable> T expectScriptThrows(Class<T> expectedType, ThrowingRunnable runnable) {
return expectScriptThrows(expectedType, true, runnable);
}
/** Checks a specific exception class is thrown (boxed inside ScriptException) and returns it. */
public static <T extends Throwable> T expectScriptThrows(Class<T> expectedType, boolean shouldHaveScriptStack,
ThrowingRunnable runnable) {
try {
runnable.run();
} catch (Throwable e) {
if (e instanceof ScriptException) {
boolean hasEmptyScriptStack = ((ScriptException) e).getScriptStack().isEmpty();
if (shouldHaveScriptStack && hasEmptyScriptStack) {
/* If this fails you *might* be missing -XX:-OmitStackTraceInFastThrow in the test jvm
* In Eclipse you can add this by default by going to Preference->Java->Installed JREs,
* clicking on the default JRE, clicking edit, and adding the flag to the
* "Default VM Arguments". */
AssertionFailedError assertion = new AssertionFailedError("ScriptException should have a scriptStack");
assertion.initCause(e);
throw assertion;
} else if (false == shouldHaveScriptStack && false == hasEmptyScriptStack) {
AssertionFailedError assertion = new AssertionFailedError("ScriptException shouldn't have a scriptStack");
assertion.initCause(e);
throw assertion;
}
e = e.getCause();
if (expectedType.isInstance(e)) {
return expectedType.cast(e);
}
} else {
AssertionFailedError assertion = new AssertionFailedError("Expected boxed ScriptException");
assertion.initCause(e);
throw assertion;
}
AssertionFailedError assertion = new AssertionFailedError("Unexpected exception type, expected "
+ expectedType.getSimpleName());
assertion.initCause(e);
throw assertion;
}
throw new AssertionFailedError("Expected exception " + expectedType.getSimpleName());
}
/**
* Asserts that the script_stack looks right.
*/
public static void assertScriptStack(ScriptException e, String... stack) {
// This particular incantation of assertions makes the error messages more useful
try {
assertThat(e.getScriptStack(), hasSize(stack.length));
for (int i = 0; i < stack.length; i++) {
assertEquals(stack[i], e.getScriptStack().get(i));
}
} catch (AssertionError assertion) {
assertion.initCause(e);
throw assertion;
}
}
}