Index: tests/isolate/function_send_test.dart |
diff --git a/tests/isolate/function_send_test.dart b/tests/isolate/function_send_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..954a6b4cc444e6cafb8ce72ba1f9f3adccd8908d |
--- /dev/null |
+++ b/tests/isolate/function_send_test.dart |
@@ -0,0 +1,184 @@ |
+// Copyright (c) 2014, the Dart 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. |
+ |
+import "dart:isolate"; |
+import "dart:async"; |
+import "package:expect/expect.dart"; |
+import "package:async_helper/async_helper.dart"; |
+ |
+void toplevel(port, message) { port.send("toplevel:$message"); } |
+Function createFuncToplevel() => (p, m) { p.send(m); }; |
+class C { |
+ Function initializer; |
+ Function body; |
+ C() : initializer = ((p, m) { throw "initializer"; }) { |
+ body = (p, m) { throw "body"; }; |
+ } |
+ static void staticFunc(port, message) { port.send("static:$message"); } |
+ static Function createFuncStatic() => (p, m) { throw "static expr"; }; |
+ void instanceMethod(p, m) { throw "instanceMethod"; } |
+ Function createFuncMember() => (p, m) { throw "instance expr"; }; |
+ void call(n, p) { throw "C"; } |
+} |
+ |
+class Callable { |
+ void call(p, m) { p.send(["callable", m]); } |
+} |
+ |
+ |
+void main() { |
+ asyncStart(); |
+ |
+ // Sendables are top-level functions and static functions only. |
+ testSendable("toplevel", toplevel); |
+ testSendable("static", C.staticFunc); |
+ |
+ // Unsendables are any closure - instance methods or function expression. |
+ var c = new C(); |
+ testUnsendable("instance method", c.instanceMethod); |
+ testUnsendable("static context expression", createFuncToplevel()); |
+ testUnsendable("static context expression", C.createFuncStatic()); |
+ testUnsendable("initializer context expression", c.initializer); |
+ testUnsendable("constructor context expression", c.body); |
+ testUnsendable("instance method context expression", c.createFuncMember()); |
+ |
+ // The result of `toplevel.call` and `staticFunc.call` may or may not be |
+ // identical to `toplevel` and `staticFunc` respectively. If they are not |
+ // equal, they may or may not be considered toplevel/static functions anyway, |
+ // and therefore sendable. The VM and dart2js curretnly disagrees on whether |
+ // `toplevel` and `toplevel.call` are identical, both allow them to be sent. |
+ // If this is ever specified to something else, use: |
+ // testUnsendable("toplevel.call", toplevel.call); |
+ // testUnsendable("static.call", C.staticFunc.call); |
+ // instead. |
+ // These two tests should be considered canaries for accidental behavior |
+ // change rather than requirements. |
+ testSendable("toplevel", toplevel.call); |
+ testSendable("static", C.staticFunc.call); |
+ |
+ // Callable objects are sendable if general objects are (VM yes, dart2js no). |
+ // It's unspecified whether arbitrary objects can be sent. If it is specified, |
+ // add a test that `new Callable()` is either sendable or unsendable. |
+ |
+ // The call method of a callable object is a closure holding the object, |
+ // not a top-level or static function, so it should be blocked, just as |
+ // a normal method. |
+ testUnsendable("callable object", new Callable().call); |
+ asyncEnd(); |
+ return; |
+} |
+ |
+// Create a receive port that expects exactly one message. |
+// Pass the message to `callback` and return the sendPort. |
+SendPort singleMessagePort(callback) { |
+ var p; |
+ p = new RawReceivePort((v) { p.close(); callback(v); }); |
+ return p.sendPort; |
+} |
+ |
+// A singleMessagePort that expects the message to be a specific value. |
+SendPort expectMessagePort(message) { |
+ asyncStart(); |
+ return singleMessagePort((v) { |
+ Expect.equals(message, v); |
+ asyncEnd(); |
+ }); |
+} |
+ |
+void testSendable(name, func) { |
+ // Function as spawn message. |
+ Isolate.spawn(callFunc, [func, expectMessagePort("$name:spawn"), "spawn"]); |
+ |
+ // Send function to same isolate. |
+ var reply = expectMessagePort("$name:direct"); |
+ singleMessagePort(callFunc).send([func, reply, "direct"]); |
+ |
+ // Send function to other isolate, call it there. |
+ reply = expectMessagePort("$name:other isolate"); |
+ callPort().then((p) { |
+ p.send([func, reply, "other isolate"]); |
+ }); |
+ |
+ // Round-trip function trough other isolate. |
+ echoPort((roundtripFunc) { |
+ Expect.identical(func, roundtripFunc, "$name:send through isolate"); |
+ }).then((port) { port.send(func); }); |
+} |
+ |
+// Creates a new isolate and a pair of ports that expect a single message |
+// to be sent to the other isolate and back to the callback function. |
+Future<SendPort> echoPort(callback(value)) { |
+ Completer completer = new Completer<SendPort>(); |
+ SendPort replyPort = singleMessagePort(callback); |
+ RawReceivePort initPort; |
+ initPort = new RawReceivePort((p) { |
+ completer.complete(p); |
+ initPort.close(); |
+ }); |
+ return Isolate.spawn(_echo, [replyPort, initPort.sendPort]) |
+ .then((isolate) => completer.future); |
+} |
+ |
+void _echo(msg) { |
+ var replyPort = msg[0]; |
+ RawReceivePort requestPort; |
+ requestPort = new RawReceivePort((msg) { |
+ replyPort.send(msg); |
+ requestPort.close(); // Single echo only. |
+ }); |
+ msg[1].send(requestPort.sendPort); |
+} |
+ |
+// Creates other isolate that waits for a single message, `msg`, on the returned |
+// port, and executes it as `msg[0](msg[1],msg[2])` in the other isolate. |
+Future<SendPort> callPort() { |
+ Completer completer = new Completer<SendPort>(); |
+ SendPort initPort = singleMessagePort(completer.complete); |
+ return Isolate.spawn(_call, initPort) |
+ .then((_) => completer.future); |
+} |
+ |
+void _call(initPort) { |
+ initPort.send(singleMessagePort(callFunc)); |
+} |
+ // Expect.throws(() { |
+ // noReply.sendPort.send([func]); |
+ // }, null, "send wrapped"); |
+ |
+ |
+void testUnsendable(name, func) { |
+ asyncStart(); |
+ Isolate.spawn(nop, func).then((v) => throw "allowed spawn direct?", |
+ onError: (e,s){ asyncEnd(); }); |
+ asyncStart(); |
+ Isolate.spawn(nop, [func]).then((v) => throw "allowed spawn wrapped?", |
+ onError: (e,s){ asyncEnd(); }); |
+ |
+ asyncStart(); |
+ var noReply = new RawReceivePort((_) { throw "Unexpected message: $_"; }); |
+ // Currently succeedes incorrectly in dart2js. |
+ Expect.throws(() { /// 01: ok |
+ noReply.sendPort.send(func); /// 01: continued |
+ }, null, "send direct"); /// 01: continued |
+ Expect.throws(() { /// 01: continued |
+ noReply.sendPort.send([func]); /// 01: continued |
+ }, null, "send wrapped"); /// 01: continued |
+ scheduleMicrotask(() { |
+ noReply.close(); |
+ asyncEnd(); |
+ }); |
+ |
+ // Try sending through other isolate. |
+ asyncStart(); |
+ echoPort((_) => throw "unreachable") |
+ .then((p) => p.send(func)) |
+ .then((p) => throw "unreachable 2", |
+ onError: (e, s) {asyncEnd();}); |
+} |
+ |
+void nop(_) {} |
+ |
+void callFunc(message) { |
+ message[0](message[1], message[2]); |
+} |