Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(432)

Unified Diff: pkg/serialization/test/serialization_test.dart

Issue 11293283: Initial version of a serialization framework (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: pkg/serialization/test/serialization_test.dart
===================================================================
--- pkg/serialization/test/serialization_test.dart (revision 0)
+++ pkg/serialization/test/serialization_test.dart (revision 0)
@@ -0,0 +1,464 @@
+// 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.
+
+library serialization_test;
+
+import '../../unittest/lib/unittest.dart';
+import '../lib/serialization.dart';
+import '../lib/src/serialization_helpers.dart';
+import '../lib/src/mirrors_helpers.dart';
+
+part 'test_models.dart';
+
+main() {
+ var p1 = new Person();
+ var a1 = new Address();
+ a1.street = 'N 34th';
+ a1.city = 'Seattle';
+
+ test('Basic extraction of a simple object', () {
+ // TODO(alanknight): Switch these to use literal types. Issue
+ var s = new Serialization()
+ ..addRuleFor(a1).configureForMaps();
+ Map extracted = states(a1, s).first;
+ expect(extracted.length, 4);
+ expect(extracted['street'], 'N 34th');
+ expect(extracted['city'], 'Seattle');
+ expect(extracted['state'], null);
+ expect(extracted['zip'], null);
+ Reader reader = setUpReader(s, extracted);
+ Address a2 = readBackSimple(s, a1, reader);
+ expect(a2.street, 'N 34th');
+ expect(a2.city, 'Seattle');
+ expect(a2.state,null);
+ expect(a2.zip, null);
+ });
+
+ test('Slightly further with a simple object', () {
+ // TODO(alanknight): Tests that rely on what index rules are going to be
+ // at are very fragile. At least abstract it to something calculated.
+ var p1 = new Person()..name = 'Alice'..address = a1;
+ var s = new Serialization()
+ ..addRuleFor(p1).configureForMaps()
+ ..addRuleFor(a1).configureForMaps();
+ // TODO(alanknight): Need a better API for getting to flat state without
+ // actually writing.
+ var w = new Writer(s);
+ w.trace.addRoot(p1);
+ w.trace.traceAll();
+ w.flatten();
+ var flatPerson = w.states[3].first;
+ var primStates = w.states.first;
+ expect(primStates.isEmpty, true);
+ expect(flatPerson["name"], "Alice");
+ var ref = flatPerson["address"];
+ expect(ref is Reference, true);
+ expect(ref.ruleNumber, 4);
+ expect(ref.objectNumber, 0);
+ expect(w.states[4].first['street'], 'N 34th');
+ });
+
+ test('exclude fields', () {
+ var s = new Serialization()
+ ..addRuleFor(a1,
+ excludeFields: ['state', 'zip']).configureForMaps();
+ var extracted = states(a1, s).first;
+ expect(extracted.length, 2);
+ expect(extracted['street'], 'N 34th');
+ expect(extracted['city'], 'Seattle');
+ Reader reader = setUpReader(s, extracted);
+ Address a2 = readBackSimple(s, a1, reader);
+ expect(a2.state, null);
+ expect(a2.city, 'Seattle');
+ });
+
+ test('list', () {
+ var list = [5, 4, 3, 2, 1];
+ var s = new Serialization();
+ var extracted = states(list, s).first;
+ expect(extracted.length, 5);
+ for (var i = 0; i < 5; i++) {
+ expect(extracted[i], (5 - i));
+ }
+ Reader reader = setUpReader(s, extracted);
+ var list2 = readBackSimple(s, list, reader);
+ expect(list, list2);
+ });
+
+ test('different kinds of fields', () {
+ var x = new Various.Foo("d", "e");
+ x.a = "a";
+ x.b = "b";
+ x._c = "c";
+ var s = new Serialization()
+ ..addRuleFor(x,
+ constructor: "Foo",
+ constructorFields: ["d", "e"]);
+ var state = states(x, s).first;
+ expect(state.length, 4);
+ var expected = "abde";
+ for (var i in [0,1,2,3]) {
+ expect(state[i], expected[i]);
+ }
+ Reader reader = setUpReader(s, state);
+ Various y = readBackSimple(s, x, reader);
+ expect(x.a, y.a);
+ expect(x.b, y.b);
+ expect(x.d, y.d);
+ expect(x.e, y.e);
+ expect(y._c, 'default value');
+ });
+
+ test('Stream', () {
+ // This is an interesting case. The Stream doesn't expose its internal
+ // collection at all, and sets it in the constructor. So to get it we
+ // read a private field and then set that via the constructor. That works
+ // but should we have some kind of large red flag that you're using private
+ // state.
+ var stream = new Stream([3,4,5]);
+ expect((stream..next()).next(), 4);
+ expect(stream.position, 2);
+ var s = new Serialization()
+ ..addRuleFor(stream,
+ constructorFields: ['_collection']);
+ var state = states(stream, s).first;
+ // Define names for the variable offsets to make this more readable.
+ var _collection = 0, position = 1;
+ expect(state[_collection],[3,4,5]);
+ expect(state[position], 2);
+ });
+
+ test('date', () {
+ var date = new Date.now();
+ var s = new Serialization()
+ ..addRuleFor(date,
+ constructorFields : ["year", "month", "day", "hour", "minute",
+ "second", "millisecond", "isUtc"])
+ .configureForMaps();
+ var state = states(date, s).first;
+ expect(state["year"],date.year);
+ expect(state["isUtc"],date.isUtc);
+ expect(state["millisecond"], date.millisecond);
+ });
+
+ test('Iteration helpers', () {
+ var map = {"a" : 1, "b" : 2, "c" : 3};
+ var list = [1, 2, 3];
+ var set = new Set.from(list);
+ var m = keysAndValues(map);
+ var l = keysAndValues(list);
+ var s = keysAndValues(set);
+
+ m.forEach((key, value) {expect(key.charCodes[0], value + 96);});
+ l.forEach((key, value) {expect(key + 1, value);});
+ var index = 0;
+ var seen = new Set();
+ s.forEach((key, value) {
+ expect(key, index++);
+ expect(seen.contains(value), isFalse);
+ seen.add(value);
+ });
+ expect(seen.length, 3);
+
+ var i = 0;
+ m = values(map);
+ l = values(list);
+ s = values(set);
+ m.forEach((each) {expect(each, ++i);});
+ i = 0;
+ l.forEach((each) {expect(each, ++i);});
+ i = 0;
+ s.forEach((each) {expect(each, ++i);});
+ i = 0;
+
+ seen = new Set();
+ for (var each in m) {
+ expect(seen.contains(each), isFalse);
+ seen.add(each);
+ }
+ expect(seen.length, 3);
+ i = 0;
+ for (var each in l) {
+ expect(each, ++i);
+ }
+ });
+
+ Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3");
+ n1.children = [n2, n3];
+ n2.parent = n1;
+ n3.parent = n1;
+
+ test('Trace a cyclical structure', () {
+ var s = new Serialization();
+ var trace = new Trace(new Writer(s));
+ trace.writer.trace = trace;
+ trace.trace(n1);
+ var all = trace.writer.references.keys;
+ expect(all.length, 4);
+ expect(all.contains(n1), isTrue);
+ expect(all.contains(n2), isTrue);
+ expect(all.contains(n3), isTrue);
+ expect(all.contains(n1.children), isTrue);
+ });
+
+ test('Flatten references in a cyclical structure', () {
+ var s = new Serialization();
+ var w = new Writer(s);
+ w.trace = new Trace(w);
+ w.write(n1);
+ expect(w.states.length, 4); // prims, lists, essential lists, basic
+ var children = 0, name = 1, parent = 2;
+ List rootNode = w.states[3].filter((x) => x[name] == "1");
+ rootNode = rootNode.first;
+ expect(rootNode[parent], isNull);
+ var list = w.states[1].first;
+ expect(w.stateForReference(rootNode[children]), list);
+ var parentNode = w.stateForReference(list[0])[parent];
+ expect(w.stateForReference(parentNode), rootNode);
+ });
+
+ test('round-trip', () {
+ runRoundTripTest(nodeSerializerReflective);
+ });
+
+ test('round-trip hard-coded', () {
+ runRoundTripTest(nodeSerializerNonReflective);
+ });
+
+ test('round-trip with essential parent', () {
+ runRoundTripTest(nodeSerializerWithEssentialParent);
+ });
+
+ test('round-trip, flat format', () {
+ runRoundTripTestFlat(nodeSerializerReflective);
+ });
+
+ test('round-trip using Maps', () {
+ runRoundTripTest(nodeSerializerUsingMaps);
+ });
+
+ test('eating your own tail', () {
+ // Create a meta-serializer, that serializes serializations, then
+ // use it to serialize a basic serialization, then run a test on the
+ // the result.
+ var s = new Serialization()
+ ..addRuleFor(new Node(''), constructorFields: ['name'])
+ ..selfDescribing = false;
+ var meta = metaSerialization();
+ var serialized = meta.write(s);
+ var s2 = new Reader(meta)
+ .readOne(serialized, {"Node" : reflect(new Node('')).type});
+ runRoundTripTest((x) => s2);
+ });
+
+ test("Verify we're not serializing lists twice if they're essential", () {
+ Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3");
+ n1.children = [n2, n3];
+ n2.parent = n1;
+ n3.parent = n1;
+ var s = new Serialization()
+ ..addRuleFor(n1, constructorFields: ["name"]).
+ specialTreatmentFor("children", (parent, child) =>
+ parent.reflectee.children = child);
+ var w = new Writer(s);
+ w.write(n1);
+ expect(w.rules[2] is ListRuleEssential, isTrue);
+ expect(w.rules[1] is ListRule, isTrue);
+ expect(w.states[1].length, 0);
+ expect(w.states[2].length, 1);
+ s = new Serialization()
+ ..addRuleFor(n1, constructorFields: ["name"]);
+ w = new Writer(s);
+ w.write(n1);
+ expect(w.states[1].length, 1);
+ expect(w.states[2].length, 0);
+ });
+
+ test('Identity of equal objects preserved', () {
+ Node n1 = new NodeEqualByName("foo"),
+ n2 = new NodeEqualByName("foo"),
+ n3 = new NodeEqualByName("3");
+ n1.children = [n2, n3];
+ n2.parent = n1;
+ n3.parent = n1;
+ var s = new Serialization()
+ ..selfDescribing = false
+ ..addRuleFor(n1, constructorFields: ["name"]);
+ var w = new Writer(s);
+ var r = new Reader(s);
+ var m1 = r.read(w.write(n1)).first;
+ var m2 = m1.children.first;
+ var m3 = m1.children.last;
+ expect(m1, m2);
+ expect(identical(m1, m2), isFalse);
+ expect(m1 == m3, isFalse);
+ expect(identical(m2.parent, m3.parent), isTrue);
+ });
+
+}
+
+/******************************************************************************
+ * The end of the tests and the beginning of various helper functions to make
+ * it easier to write the repetitive sections.
+ ******************************************************************************/
+
+/** Create a Serialization for serializing Serializations. */
+Serialization metaSerialization() {
+ // Make some bogus rule instances so we have something to feed rule creation
+ // and get their types. If only we had class literals implemented...
+ var closureRule = new ClosureToMapRule.stub([].runtimeType);
+ var basicRule = new BasicRule(reflect(null).type, '', [], [], []);
+
+ var meta = new Serialization()
+ ..selfDescribing = false
+ ..addRuleFor(new ListRule())
+ ..addRuleFor(new PrimitiveRule())
+ // TODO(alanknight): Handle the ClosureToMapRule as well.
+ // Note that we're passing in a constant for one of the fields.
+ ..addRuleFor(basicRule,
+ constructorFields: ['typeWrapped',
+ 'constructorName',
+ 'constructorFields', 'regularFields', []],
+ fields: [])
+ ..addRuleFor(new Serialization()).specialTreatmentFor('rules',
+ (InstanceMirror s, List rules) {
+ rules.forEach((x) => s.reflectee.addRule(x));
+ })
+ ..addRule(new ClassMirrorRule());
+ return meta;
+}
+
+/**
+ * Read back a simple object, assumed to be the only one of its class in the
+ * reader.
+ */
+readBackSimple(Serialization s, object, Reader reader) {
+ var rule = s.rulesFor(object)[0];
+ reader.inflateForRule(rule);
+ var list2 = reader.allObjectsForRule(rule)[0];
+ return list2;
+}
+
+/**
+ * Set up a basic reader with some fake data. Hard-codes the assumption
+ * of how many rules there are.
+ */
+Reader setUpReader(aSerialization, sampleData) {
+ var reader = new Reader(aSerialization);
+ // We're not sure which rule needs the sample data, so put it everywhere
+ // and trust that the extra will just be ignored.
+ reader.data = [[sampleData], [sampleData], [sampleData], [sampleData]];
+ return reader;
+}
+
+/** Return a serialization for Node objects, using a reflective rule. */
+Serialization nodeSerializerReflective(Node n) {
+ return new Serialization()
+ ..addRuleFor(n, constructorFields: ["name"])
+ ..externalObjects['Node'] = reflect(new Node('')).type;
+}
+
+/**
+ * Return a serialization for Node objects but using Maps for the internal
+ * representation rather than lists.
+ */
+Serialization nodeSerializerUsingMaps(Node n) {
+ return new Serialization()
+ ..addRuleFor(n, constructorFields: ["name"]).configureForMaps()
+ ..externalObjects['Node'] = reflect(new Node('')).type;
+}
+
+/**
+ * Return a serialization for Node objects where the "parent" instance
+ * variable is considered essential state.
+ */
+Serialization nodeSerializerWithEssentialParent(Node n) {
+ var s = new Serialization()
+ ..addRuleFor(
+ n,
+ constructor: "parentEssential",
+ constructorFields: ["parent"])
+ ..externalObjects['Node'] = reflect(new Node('')).type
+ ..selfDescribing = false;
+
+ // Force the node rule to be first, in order to make a cycle which would
+ // not cause a problem if we handled the list first, because the list
+ // considers all of its state non-essential, thus breaking the cycle.
+ s.rules = append([s.rules.removeLast()], s.rules);
+ keysAndValues(s.rules).forEach((index, rule) => rule.number = index);
+ return s;
+}
+
+/** Return a serialization for Node objects using a ClosureToMapRule. */
+Serialization nodeSerializerNonReflective(Node n) {
+ var rule = new ClosureToMapRule(
+ n.runtimeType,
+ (o) => {"name" : o.name, "children" : o.children, "parent" : o.parent},
+ (map) => new Node(map["name"]),
+ (map, object) { object
Jennifer Messerly 2012/11/20 01:20:47 personally I'd put "object" on next line, and only
Alan Knight 2012/11/20 12:17:54 Done.
+ ..children = map["children"]
+ ..parent = map["parent"];
+ });
+ return new Serialization()
+ ..selfDescribing = false
+ ..addRule(rule);
+}
+
+/**
+ * Run a round-trip test on a simple tree of nodes, using a serialization
+ * that's returned by the [serializerSetup] function.
+ */
+runRoundTripTest(Function serializerSetUp) {
+ Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3");
+ n1.children = [n2, n3];
+ n2.parent = n1;
+ n3.parent = n1;
+ var s = serializerSetUp(n1);
+ var output = s.write(n2);
+ var s2 = serializerSetUp(n1);
+ var reader = new Reader(s2);
+ var m2 = reader.readOne(output);
+ var m1 = m2.parent;
+ expect(m1 is Node, isTrue);
+ var children = m1.children;
+ expect(m1.name,"1");
+ var m3 = m1.children.last;
+ expect(m2.name, "2");
+ expect(m3.name, "3");
+ expect(m2.parent, m1);
+ expect(m3.parent, m1);
+ expect(m1.parent, isNull);
+}
+
+/**
+ * Run a round-trip test on a simple of nodes, but using the flat format
+ * rather than the maps.
+ */
+runRoundTripTestFlat(serializerSetUp) {
+ Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3");
+ n1.children = [n2, n3];
+ n2.parent = n1;
+ n3.parent = n1;
+ var s = serializerSetUp(n1);
+ var output = s.writeFlat(n2);
+ var s2 = serializerSetUp(n1);
+ var reader = new Reader(s2);
+ var m2 = reader.readFlat(output).first;
+ var m1 = m2.parent;
+ expect(m1 is Node, isTrue);
+ var children = m1.children;
+ expect(m1.name,"1");
+ var m3 = m1.children.last;
+ expect(m2.name, "2");
+ expect(m3.name, "3");
+ expect(m2.parent, m1);
+ expect(m3.parent, m1);
+ expect(m1.parent, isNull);
+}
+
+/** Extract the state from [object] using the rules in [s] and return it. */
+states(Object object, Serialization s) {
+ var rules = s.rulesFor(object);
+ return rules.map( (x) => x.extractState(object, doNothing));
Jennifer Messerly 2012/11/20 01:20:47 looks like an extra space here
Alan Knight 2012/11/20 12:17:54 Done.
+}

Powered by Google App Engine
This is Rietveld 408576698