// Copyright (c) 2012, 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. // VMOptions=--enable-isolate-groups --experimental-enable-isolate-groups-jit // VMOptions=--no-enable-isolate-groups // Dart test program for testing serialization of messages. // VMOptions=--enable_type_checks --enable_asserts library MessageTest; import 'dart:async'; import 'dart:collection'; import 'dart:isolate'; import 'package:async_helper/async_helper.dart'; import 'package:expect/expect.dart'; import 'dart:typed_data'; void echoMain(msg) { SendPort replyTo = msg[0]; SendPort pong = msg[1]; ReceivePort port = new ReceivePort(); replyTo.send(port.sendPort); port.listen((msg) { if (msg == "halt") { port.close(); } else { pong.send(msg); } }); } class A { var field = 499; A(); A.named(this.field); } class B extends A { final field2; B() : field2 = 99; B.named(this.field2, x) : super.named(x); } class C extends B { var field = 33; get superField => super.field; get superField2 => super.field2; } class M { get field2 => 11; } class D extends C with M { var gee = 123; } class E { // Make sure E.fun is not removed by the tree shaker, as this test verifies // that an object with a tear-off in E.fun cannot be sent. @pragma("vm:entry-point") Function fun; E(this.fun); static fooFun() => 499; instanceFun() => 1234; } barFun() => 42; class F { final field = "field"; const F(); } class G { final field; const G(this.field); } class Value { final val; Value(this.val); operator ==(other) { if (other is! Value) return false; return other.val == val; } get hashCode => val; } void runTests(SendPort ping, Queue checks) { ping.send("abc"); checks.add((x) => Expect.equals("abc", x)); ping.send([1, 2]); checks.add((x) { Expect.isTrue(x is List); Expect.listEquals([1, 2], x); // Make sure the list is mutable. x[0] = 0; Expect.equals(0, x[0]); // List must be extendable. x.add(3); Expect.equals(3, x[2]); }); var fixed = List.filled(2, 0); fixed[0] = 0; fixed[1] = 1; ping.send(fixed); checks.add((x) { Expect.isTrue(x is List); Expect.listEquals([0, 1], x); // List must be mutable. x[0] = 3; Expect.equals(3, x[0]); // List must be fixed length. Expect.throws(() { x.add(5); }); }); List cyclic = []; cyclic.add(cyclic); ping.send(cyclic); checks.add((x) { Expect.isTrue(x is List); Expect.equals(1, x.length); Expect.identical(x, x[0]); // List must be mutable. x[0] = 55; Expect.equals(55, x[0]); // List must be extendable. x.add(42); Expect.equals(42, x[1]); }); var cyclic2 = List.filled(1, null); cyclic2[0] = cyclic2; ping.send(cyclic2); checks.add((x) { Expect.isTrue(x is List); Expect.equals(1, x.length); Expect.identical(x, x[0]); // List must be mutable. x[0] = 55; Expect.equals(55, x[0]); // List must be fixed. Expect.throws(() => x.add(42)); }); List constList = const [1, 2]; ping.send(constList); checks.add((x) { Expect.isTrue(x is List); Expect.listEquals([1, 2], x); // Make sure the list is immutable. Expect.throws(() => x[0] = 0); // //# constList: ok // List must not be extendable. Expect.throws(() => x.add(3)); Expect.identical(x, constList); // //# constList_identical: ok }); Uint8List uint8 = new Uint8List(2); uint8[0] = 0; uint8[1] = 1; ping.send(uint8); checks.add((x) { Expect.isTrue(x is Uint8List); Expect.equals(2, x.length); Expect.equals(0, x[0]); Expect.equals(1, x[1]); }); Uint16List uint16 = new Uint16List(2); uint16[0] = 0; uint16[1] = 1; ByteBuffer byteBuffer = uint16.buffer; ping.send(byteBuffer); // //# byteBuffer: ok checks.add( // //# byteBuffer: ok (x) { Expect.isTrue(x is ByteBuffer); Uint16List uint16View = new Uint16List.view(x); Expect.equals(2, uint16View.length); Expect.equals(0, uint16View[0]); Expect.equals(1, uint16View[1]); } ) // //# byteBuffer: ok ; Int32x4List list32x4 = new Int32x4List(2); list32x4[0] = new Int32x4(1, 2, 3, 4); list32x4[1] = new Int32x4(5, 6, 7, 8); ping.send(list32x4); checks.add( (x) { Expect.isTrue(x is Int32x4List); Expect.equals(2, x.length); Int32x4 entry1 = x[0]; Int32x4 entry2 = x[1]; Expect.equals(1, entry1.x); Expect.equals(2, entry1.y); Expect.equals(3, entry1.z); Expect.equals(4, entry1.w); Expect.equals(5, entry2.x); Expect.equals(6, entry2.y); Expect.equals(7, entry2.z); Expect.equals(8, entry2.w); }); Float32x4List flist32x4 = new Float32x4List(2); flist32x4[0] = new Float32x4(1.5, 2.5, 3.5, 4.5); flist32x4[1] = new Float32x4(5.5, 6.5, 7.5, 8.5); ping.send(flist32x4); checks.add( (x) { Expect.isTrue(x is Float32x4List); Expect.equals(2, x.length); Float32x4 entry1 = x[0]; Float32x4 entry2 = x[1]; Expect.equals(1.5, entry1.x); Expect.equals(2.5, entry1.y); Expect.equals(3.5, entry1.z); Expect.equals(4.5, entry1.w); Expect.equals(5.5, entry2.x); Expect.equals(6.5, entry2.y); Expect.equals(7.5, entry2.z); Expect.equals(8.5, entry2.w); }); Float64x2List flist64x2 = new Float64x2List(2); flist64x2[0] = new Float64x2(1.5, 2.5); flist64x2[1] = new Float64x2(5.5, 6.5); ping.send(flist64x2); checks.add( (x) { Expect.isTrue(x is Float64x2List); Expect.equals(2, x.length); Float64x2 entry1 = x[0]; Float64x2 entry2 = x[1]; Expect.equals(1.5, entry1.x); Expect.equals(2.5, entry1.y); Expect.equals(5.5, entry2.x); Expect.equals(6.5, entry2.y); }); ping.send({"foo": 499, "bar": 32}); checks.add((x) { Expect.isTrue(x is LinkedHashMap); Expect.listEquals(["foo", "bar"], x.keys.toList()); Expect.listEquals([499, 32], x.values.toList()); Expect.equals(499, x["foo"]); Expect.equals(32, x["bar"]); // Must be mutable. x["foo"] = 22; Expect.equals(22, x["foo"]); // Must be extendable. x["gee"] = 499; Expect.equals(499, x["gee"]); }); Map mapWithRemovedKey = {"foo": 499, "bar": 32}; mapWithRemovedKey.remove("foo"); ping.send(mapWithRemovedKey); checks.add((x) { Expect.isTrue(x is LinkedHashMap); Expect.listEquals(["bar"], x.keys.toList()); Expect.listEquals([32], x.values.toList()); Expect.equals(32, x["bar"]); }); // Test map where a key does not define ==/hashCode. Map mapWithIdentityKey = new Map(); mapWithIdentityKey[new A()] = 499; ping.send(mapWithIdentityKey); checks.add((x) { Expect.isTrue(x is LinkedHashMap); int value = x.values.first; Expect.equals(499, value); A key = x.keys.first; Expect.equals(499, x[key]); }); ping.send({0: 499, 1: 32}); checks.add((x) { Expect.isTrue(x is LinkedHashMap); Expect.listEquals([0, 1], x.keys.toList()); Expect.listEquals([499, 32], x.values.toList()); // Must be mutable. x[0] = 22; Expect.equals(22, x[0]); // Must be extendable. x[123] = 499; Expect.equals(499, x[123]); }); Map cyclicMap = {}; cyclicMap["cycle"] = cyclicMap; ping.send(cyclicMap); checks.add((x) { Expect.isTrue(x is LinkedHashMap); Expect.identical(x, x["cycle"]); // Must be mutable. x["cycle"] = 22; Expect.equals(22, x["cycle"]); // Must be extendable. x["gee"] = 499; Expect.equals(499, x["gee"]); }); Map constMap = const {'foo': 499}; ping.send(constMap); checks.add((x) { Expect.isTrue(x is Map); print(x.length); Expect.equals(1, x.length); Expect.equals(499, x['foo']); Expect.identical(constMap, x); // //# constMap: ok Expect.throws(() => constMap['bar'] = 42); }); ping.send(new A()); checks.add((x) { Expect.isTrue(x is A); Expect.equals(499, x.field); }); ping.send(new A.named(42)); checks.add((x) { Expect.isTrue(x is A); Expect.equals(42, x.field); }); ping.send(new B()); checks.add((x) { Expect.isTrue(x is A); Expect.isTrue(x is B); Expect.equals(499, x.field); Expect.equals(99, x.field2); Expect.throws(() => x.field2 = 22); }); ping.send(new B.named(1, 2)); checks.add((x) { Expect.isTrue(x is A); Expect.isTrue(x is B); Expect.equals(2, x.field); Expect.equals(1, x.field2); Expect.throws(() => x.field2 = 22); }); ping.send(new C()); checks.add((x) { Expect.isTrue(x is A); Expect.isTrue(x is B); Expect.isTrue(x is C); Expect.equals(33, x.field); Expect.equals(99, x.field2); Expect.equals(499, x.superField); Expect.throws(() => x.field2 = 22); }); ping.send(new D()); checks.add((x) { Expect.isTrue(x is A); Expect.isTrue(x is B); Expect.isTrue(x is C); Expect.isTrue(x is D); Expect.isTrue(x is M); Expect.equals(33, x.field); Expect.equals(11, x.field2); Expect.equals(499, x.superField); Expect.equals(99, x.superField2); Expect.throws(() => x.field2 = 22); }); D cyclicD = new D(); ping.send(cyclicD); checks.add((x) { Expect.isTrue(x is A); Expect.isTrue(x is B); Expect.isTrue(x is C); Expect.isTrue(x is D); Expect.isTrue(x is M); Expect.equals(11, x.field2); Expect.equals(499, x.superField); Expect.equals(99, x.superField2); Expect.throws(() => x.field2 = 22); }); ping.send(new E(E.fooFun)); // //# fun: ok checks.add((x) { // //# fun: continued Expect.equals(E.fooFun, x.fun); // //# fun: continued Expect.equals(499, x.fun()); // //# fun: continued }); // //# fun: continued ping.send(new E(barFun)); // //# fun: continued checks.add((x) { // //# fun: continued Expect.equals(barFun, x.fun); // //# fun: continued Expect.equals(42, x.fun()); // //# fun: continued }); // //# fun: continued Expect.throws(() => ping.send(new E(new E(E.fooFun).instanceFun))); F nonConstF = new F(); ping.send(nonConstF); checks.add((x) { Expect.equals("field", x.field); Expect.isFalse(identical(nonConstF, x)); }); const F constF = const F(); ping.send(constF); checks.add((x) { Expect.equals("field", x.field); Expect.identical(constF, x); // //# constInstance: ok }); G g1 = new G(nonConstF); G g2 = new G(constF); G g3 = const G(constF); ping.send(g1); ping.send(g2); ping.send(g3); checks.add((x) { // g1. Expect.isTrue(x is G); Expect.isFalse(identical(g1, x)); F f = x.field; Expect.equals("field", f.field); Expect.isFalse(identical(nonConstF, f)); }); checks.add((x) { // g2. Expect.isTrue(x is G); Expect.isFalse(identical(g1, x)); F f = x.field; Expect.equals("field", f.field); Expect.identical(constF, f); // //# constInstance: continued }); checks.add((x) { // g3. Expect.isTrue(x is G); Expect.identical(g3, x); // //# constInstance: continued F f = x.field; Expect.equals("field", f.field); Expect.identical(constF, f); // //# constInstance: continued }); // Make sure objects in a map are serialized and deserialized in the correct // order. Map m = new Map(); Value val1 = new Value(1); Value val2 = new Value(2); m[val1] = val2; m[val2] = val1; // Possible bug we want to catch: // serializer runs through keys first, and then the values: // - id1 = val1, id2 = val2, ref[id2], ref[id1] // deserializer runs through the keys and values in order: // - val1; // id1. // - ref[id2]; // boom. Wasn't deserialized yet. ping.send(m); checks.add((x) { Expect.isTrue(x is Map); Expect.equals(2, x.length); Expect.equals(val2, x[val1]); Expect.equals(val1, x[val2]); Expect.identical(x.keys.elementAt(0), x.values.elementAt(1)); Expect.identical(x.keys.elementAt(1), x.values.elementAt(0)); }); } void main() { asyncStart(); Queue checks = new Queue(); ReceivePort testPort = new ReceivePort(); Completer completer = new Completer(); testPort.listen((msg) { Function check = checks.removeFirst(); check(msg); if (checks.isEmpty) { completer.complete(); testPort.close(); } }); ReceivePort initialReplyPort = new ReceivePort(); Isolate .spawn(echoMain, [initialReplyPort.sendPort, testPort.sendPort]) .then((_) => initialReplyPort.first) .then((_ping) { SendPort ping = _ping; runTests(ping, checks); Expect.isTrue(checks.length > 0); completer.future.then((_) => ping.send("halt")).then((_) => asyncEnd()); }); }