Last active
February 21, 2017 10:01
-
-
Save jfrantzius/0a40c963413bdeabb51ecb13a769a436 to your computer and use it in GitHub Desktop.
Failing test that verifies closures aren't thread-safe
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.aperto.javascript; | |
import org.junit.Assert; | |
import org.junit.Test; | |
import javax.script.ScriptContext; | |
import javax.script.ScriptEngine; | |
import javax.script.ScriptEngineManager; | |
import javax.script.ScriptException; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.concurrent.atomic.AtomicInteger; | |
import java.util.function.IntConsumer; | |
import java.util.stream.IntStream; | |
import jdk.nashorn.api.scripting.ScriptObjectMirror; | |
/** | |
* Some tests on Javascript closure behaviour. | |
* @author joerg.frantzius | |
* | |
*/ | |
public class NashornClosureTest { | |
/** | |
* Succeeds because holds function pointer in closure, instead of String or Thread object. | |
*/ | |
@Test | |
public void testClosureThreadSafety2() throws ScriptException { | |
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); | |
String testJsFunction = ( | |
" (function outerFunction(currentThreadFunction) {\n" + | |
" function innerFunction() {\n" + | |
" return currentThreadFunction().toString();\n" + | |
" }\n" + | |
" return innerFunction;\n" + | |
" })(java.lang.Thread.currentThread)\n"); | |
ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval(testJsFunction); | |
IntConsumer invokeAndTest = i-> { | |
Object received = jsFunction.call(jsFunction); | |
Assert.assertEquals(Thread.currentThread().toString(), received); | |
}; | |
IntStream.range(0, 10).parallel().forEach(invokeAndTest); | |
} | |
/** | |
* Fails by design of Javascript closures (using a Thread object instead of String object). | |
*/ | |
@Test | |
public void testClosureThreadSafetyWithThreadObject() throws ScriptException { | |
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); | |
String testJsFunction = ( | |
" (function outerFunction(threadPointer) {\n" + | |
" function innerFunction() {\n" + | |
" return threadPointer.toString();\n" + | |
" }\n" + | |
" return innerFunction;\n" + | |
" })(java.lang.Thread.currentThread())\n"); | |
ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval(testJsFunction); | |
IntConsumer invokeAndTest = i-> { | |
Object received = jsFunction.call(jsFunction); | |
Assert.assertEquals(Thread.currentThread().toString(), received); | |
}; | |
IntStream.range(0, 10).parallel().forEach(invokeAndTest); | |
} | |
/** | |
* Fails by design of Javascript closures. | |
*/ | |
@Test | |
public void testClosureThreadSafetyWithString() throws ScriptException { | |
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); | |
String testJsFunction = ( | |
" (function outerFunction(currentThreadName) {\n" + | |
" function innerFunction() {\n" + | |
" return currentThreadName;\n" + | |
" }\n" + | |
" return innerFunction;\n" + | |
" })(java.lang.Thread.currentThread().toString())\n"); | |
ScriptObjectMirror jsFunction = (ScriptObjectMirror) engine.eval(testJsFunction); | |
IntConsumer invokeAndTest = i-> { | |
Object received = jsFunction.call(jsFunction); | |
Assert.assertEquals(Thread.currentThread().toString(), received); | |
}; | |
IntStream.range(0, 10).parallel().forEach(invokeAndTest); | |
} | |
@Test | |
public void testClosureMutation() throws ScriptException, InterruptedException { | |
Map<String, String> mutableParameter = new HashMap<>(); | |
mutableParameter.put("value", "foo"); | |
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); | |
String testJsFunction = ( | |
"(function outerFunction(mutable) {\n" + | |
" function innerFunction() {\n" + | |
" return mutable.value;\n" + | |
" }\n" + | |
" return innerFunction;\n" + | |
"})\n"); | |
ScriptObjectMirror outerFunction = (ScriptObjectMirror) engine.eval(testJsFunction); | |
ScriptObjectMirror innerFunction = (ScriptObjectMirror) outerFunction.call(outerFunction, mutableParameter); | |
// verify function works as expected | |
Assert.assertEquals("foo", innerFunction.call(innerFunction)); | |
// mutate closure | |
mutableParameter.put("value", "bar"); | |
// call inner function again | |
Assert.assertEquals("bar", innerFunction.call(innerFunction)); | |
} | |
@Test | |
public void testClosureMutationInOtherThread() throws ScriptException, InterruptedException { | |
String currentThreadName = Thread.currentThread().toString(); | |
Map<String, String> mutableParameter = new HashMap<>(); | |
mutableParameter.put("value", currentThreadName); | |
final ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn"); | |
String testJsFunction = ( | |
"(function outerFunction(mutable) {\n" + | |
" function innerFunction() {\n" + | |
" return mutable.value;\n" + | |
" }\n" + | |
" return innerFunction;\n" + | |
"})\n"); | |
ScriptObjectMirror outerFunction = (ScriptObjectMirror) engine.eval(testJsFunction); | |
ScriptObjectMirror innerFunction = (ScriptObjectMirror) outerFunction.call(outerFunction, mutableParameter); | |
// verify function works as expected | |
Assert.assertEquals(currentThreadName, innerFunction.call(innerFunction)); | |
// mutate closure in other thread | |
Thread thread = new Thread(() -> mutableParameter.put("value", Thread.currentThread().toString())); | |
thread.start(); | |
thread.join(); | |
// call inner function again, verify it does not return currentThreadName anymore | |
Assert.assertNotEquals(currentThreadName, innerFunction.call(innerFunction)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment