Skip to content

Commit

Permalink
change from nashorn to GraalVM
Browse files Browse the repository at this point in the history
  • Loading branch information
VictorHarbo committed May 1, 2024
1 parent e16b5ff commit 4891810
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 55 deletions.
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,19 @@
<version>3.1</version>
</dependency>


<!-- GraalVM dependencies are used for JavaScript testing. -->
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>21.3.0</version>
</dependency>
<dependency>
<groupId>org.graalvm.js</groupId>
<artifactId>js</artifactId>
<version>21.3.0</version>
</dependency>

</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,26 @@


import dk.kb.netarchivesuite.solrwayback.util.FileUtil;
import org.openjdk.nashorn.api.scripting.JSObject;
import org.junit.Before;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Value;
import org.junit.Test;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
* Unittest method to call the query parser defined in the VUE typescript. The method has been copied to a new file and is now pure javascript.
* Unittest method to call the query parser defined in the VUE typescript.
* The method has been copied to a new file and is now pure javascript.
*/
public class JavascriptTests {

//The following two string will patch the java nashorn (ECMA version 5) engine to support the includes function on string and arrays (introduced in EMCA version 6)
// Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
public static final String NASHORN_POLYFILL_STRING_PROTOTYPE_INCLUDES = "if (!String.prototype.includes) { Object.defineProperty(String.prototype, 'includes', { value: function(search, start) { if (typeof start !== 'number') { start = 0 } if (start + search.length > this.length) { return false } else { return this.indexOf(search, start) !== -1 } } }) }";
// Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill
public static final String NASHORN_POLYFILL_ARRAY_PROTOTYPE_INCLUDES = "if (!Array.prototype.includes) { Object.defineProperty(Array.prototype, 'includes', { value: function(valueToFind, fromIndex) { if (this == null) { throw new TypeError('\"this\" is null or not defined'); } var o = Object(this); var len = o.length >>> 0; if (len === 0) { return false; } var n = fromIndex | 0; var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); function sameValueZero(x, y) { return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y)); } while (k < len) { if (sameValueZero(o[k], valueToFind)) { return true; } k++; } return false; } }); }";

private Invocable inv;

@Before
public void initScripts() throws Exception {
ScriptEngine scriptEngine = getJavascriptScriptEnginePatched();
scriptEngine.eval(extractQueryChecker());
inv = (Invocable) scriptEngine;
}

@Test
public void testBooleanCase() throws Exception {
assertClean("foo AND bar");
Expand Down Expand Up @@ -104,28 +86,31 @@ public void testMix() throws Exception {
assertWarning("foo:‟bar‟ AND foo:bar:zoo", "use simple quote signs", "Two colons without quote signs");
}

public void disabledtestExampleQuery() throws Exception {
String query="abc ( def AND or and [ def";
List<String> results = getQueryParseResultFromJavascript(query);
System.out.println(results);
}

/**
* Calls {@code checkQueryForBadSyntax} with {@code query} and checks that the result is the {@code expectedMessage}.
* @param query a query as used in SolrWayback.
* @param expectedContains the response from the sanity checker should contain this text.
*/
private void assertWarning(String query, String... expectedContains) throws Exception {
String warning = getQueryParseResultFromJavascript(query).toString();
if (expectedContains.length == 0) { // Any warning
assertFalse("Sanity checking the query '" + query + "' should deliver a warning",
warning.isEmpty());
}

for (String expected: expectedContains) {
assertTrue("Sanity checking the query '" + query + "' should deliver a warning containing '" +
expected + "' but delivered '" + warning + "'",
warning.contains(expected));
// Defining the engine explicitly so that testing doesn't warn about no runtime compilation of JavaScript
Engine engine = Engine.newBuilder().option("engine.WarnInterpreterOnly", "false").build();
try (Context context = Context.newBuilder("js").engine(engine).allowIO(true).build()) {

String jsCode = extractQueryChecker();
context.eval("js", jsCode);

String warning = getQueryParseResultFromJavascript(query, context).toString();
if (expectedContains.length == 0) { // Any warning
assertFalse("Sanity checking the query '" + query + "' should deliver a warning",
warning.isEmpty());
}

for (String expected : expectedContains) {
assertTrue("Sanity checking the query '" + query + "' should deliver a warning containing '" +
expected + "' but delivered '" + warning + "'",
warning.contains(expected));
}
}
}

Expand All @@ -134,25 +119,21 @@ private void assertWarning(String query, String... expectedContains) throws Exce
* @param query a query as used in SolrWayback.
*/
private void assertClean(String query) throws Exception {
String warning = getQueryParseResultFromJavascript(query).toString();
assertTrue("Sanity checking the query '" + query + "' should deliver no warning but returned'" +
warning + "'", "[]".equals(warning));
}

private List<String> getQueryParseResultFromJavascript(String query) throws Exception{
JSObject obj = (JSObject) inv.invokeFunction("checkQueryForBadSyntax", query);
return obj.values().stream().map(Object::toString).collect(Collectors.toList());
// Defining the engine explicitly so that testing doesn't warn about no runtime compilation of JavaScript
Engine engine = Engine.newBuilder().option("engine.WarnInterpreterOnly", "false").build();
try (Context context = Context.newBuilder("js").engine(engine).allowIO(true).build()) {
String jsCode = extractQueryChecker();
context.eval("js", jsCode);

String warning = getQueryParseResultFromJavascript(query, context).toString();
assertTrue("Sanity checking the query '" + query + "' should deliver no warning but returned'" +
warning + "'", "[]".equals(warning));
}
}

private static ScriptEngine getJavascriptScriptEnginePatched() throws Exception {
final ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
final ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("nashorn");

//Define the .includes function in the script engine.
scriptEngine.eval(NASHORN_POLYFILL_ARRAY_PROTOTYPE_INCLUDES);
scriptEngine.eval(NASHORN_POLYFILL_STRING_PROTOTYPE_INCLUDES);

return scriptEngine;
private Value getQueryParseResultFromJavascript(String query, Context context){
Value checkQueryForBadSyntax = context.getBindings("js").getMember("checkQueryForBadSyntax");
return checkQueryForBadSyntax.execute(query);
}

/**
Expand Down

0 comments on commit 4891810

Please sign in to comment.