Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library serialization_test; | |
| 6 | |
| 7 import '../../unittest/lib/unittest.dart'; | |
| 8 import '../lib/serialization.dart'; | |
| 9 import '../lib/src/serialization_helpers.dart'; | |
| 10 import '../lib/src/mirrors_helpers.dart'; | |
| 11 | |
| 12 part 'test_models.dart'; | |
| 13 | |
| 14 main() { | |
| 15 var p1 = new Person(); | |
| 16 var a1 = new Address(); | |
| 17 a1.street = 'N 34th'; | |
| 18 a1.city = 'Seattle'; | |
| 19 | |
| 20 test('Basic extraction of a simple object', () { | |
| 21 // TODO(alanknight): Switch these to use literal types. Issue | |
| 22 var s = new Serialization() | |
| 23 ..addRuleFor(a1).configureForMaps(); | |
| 24 Map extracted = states(a1, s).first; | |
| 25 expect(extracted.length, 4); | |
| 26 expect(extracted['street'], 'N 34th'); | |
| 27 expect(extracted['city'], 'Seattle'); | |
| 28 expect(extracted['state'], null); | |
| 29 expect(extracted['zip'], null); | |
| 30 Reader reader = setUpReader(s, extracted); | |
| 31 Address a2 = readBackSimple(s, a1, reader); | |
| 32 expect(a2.street, 'N 34th'); | |
| 33 expect(a2.city, 'Seattle'); | |
| 34 expect(a2.state,null); | |
| 35 expect(a2.zip, null); | |
| 36 }); | |
| 37 | |
| 38 test('Slightly further with a simple object', () { | |
| 39 // TODO(alanknight): Tests that rely on what index rules are going to be | |
| 40 // at are very fragile. At least abstract it to something calculated. | |
| 41 var p1 = new Person()..name = 'Alice'..address = a1; | |
| 42 var s = new Serialization() | |
| 43 ..addRuleFor(p1).configureForMaps() | |
| 44 ..addRuleFor(a1).configureForMaps(); | |
| 45 // TODO(alanknight): Need a better API for getting to flat state without | |
| 46 // actually writing. | |
| 47 var w = new Writer(s); | |
| 48 w.trace.addRoot(p1); | |
| 49 w.trace.traceAll(); | |
| 50 w.flatten(); | |
| 51 var flatPerson = w.states[3].first; | |
| 52 var primStates = w.states.first; | |
| 53 expect(primStates.isEmpty, true); | |
| 54 expect(flatPerson["name"], "Alice"); | |
| 55 var ref = flatPerson["address"]; | |
| 56 expect(ref is Reference, true); | |
| 57 expect(ref.ruleNumber, 4); | |
| 58 expect(ref.objectNumber, 0); | |
| 59 expect(w.states[4].first['street'], 'N 34th'); | |
| 60 }); | |
| 61 | |
| 62 test('exclude fields', () { | |
| 63 var s = new Serialization() | |
| 64 ..addRuleFor(a1, | |
| 65 excludeFields: ['state', 'zip']).configureForMaps(); | |
| 66 var extracted = states(a1, s).first; | |
| 67 expect(extracted.length, 2); | |
| 68 expect(extracted['street'], 'N 34th'); | |
| 69 expect(extracted['city'], 'Seattle'); | |
| 70 Reader reader = setUpReader(s, extracted); | |
| 71 Address a2 = readBackSimple(s, a1, reader); | |
| 72 expect(a2.state, null); | |
| 73 expect(a2.city, 'Seattle'); | |
| 74 }); | |
| 75 | |
| 76 test('list', () { | |
| 77 var list = [5, 4, 3, 2, 1]; | |
| 78 var s = new Serialization(); | |
| 79 var extracted = states(list, s).first; | |
| 80 expect(extracted.length, 5); | |
| 81 for (var i = 0; i < 5; i++) { | |
| 82 expect(extracted[i], (5 - i)); | |
| 83 } | |
| 84 Reader reader = setUpReader(s, extracted); | |
| 85 var list2 = readBackSimple(s, list, reader); | |
| 86 expect(list, list2); | |
| 87 }); | |
| 88 | |
| 89 test('different kinds of fields', () { | |
| 90 var x = new Various.Foo("d", "e"); | |
| 91 x.a = "a"; | |
| 92 x.b = "b"; | |
| 93 x._c = "c"; | |
| 94 var s = new Serialization() | |
| 95 ..addRuleFor(x, | |
| 96 constructor: "Foo", | |
| 97 constructorFields: ["d", "e"]); | |
| 98 var state = states(x, s).first; | |
| 99 expect(state.length, 4); | |
| 100 var expected = "abde"; | |
| 101 for (var i in [0,1,2,3]) { | |
| 102 expect(state[i], expected[i]); | |
| 103 } | |
| 104 Reader reader = setUpReader(s, state); | |
| 105 Various y = readBackSimple(s, x, reader); | |
| 106 expect(x.a, y.a); | |
| 107 expect(x.b, y.b); | |
| 108 expect(x.d, y.d); | |
| 109 expect(x.e, y.e); | |
| 110 expect(y._c, 'default value'); | |
| 111 }); | |
| 112 | |
| 113 test('Stream', () { | |
| 114 // This is an interesting case. The Stream doesn't expose its internal | |
| 115 // collection at all, and sets it in the constructor. So to get it we | |
| 116 // read a private field and then set that via the constructor. That works | |
| 117 // but should we have some kind of large red flag that you're using private | |
| 118 // state. | |
| 119 var stream = new Stream([3,4,5]); | |
| 120 expect((stream..next()).next(), 4); | |
| 121 expect(stream.position, 2); | |
| 122 var s = new Serialization() | |
| 123 ..addRuleFor(stream, | |
| 124 constructorFields: ['_collection']); | |
| 125 var state = states(stream, s).first; | |
| 126 // Define names for the variable offsets to make this more readable. | |
| 127 var _collection = 0, position = 1; | |
| 128 expect(state[_collection],[3,4,5]); | |
| 129 expect(state[position], 2); | |
| 130 }); | |
| 131 | |
| 132 test('date', () { | |
| 133 var date = new Date.now(); | |
| 134 var s = new Serialization() | |
| 135 ..addRuleFor(date, | |
| 136 constructorFields : ["year", "month", "day", "hour", "minute", | |
| 137 "second", "millisecond", "isUtc"]) | |
| 138 .configureForMaps(); | |
| 139 var state = states(date, s).first; | |
| 140 expect(state["year"],date.year); | |
| 141 expect(state["isUtc"],date.isUtc); | |
| 142 expect(state["millisecond"], date.millisecond); | |
| 143 }); | |
| 144 | |
| 145 test('Iteration helpers', () { | |
| 146 var map = {"a" : 1, "b" : 2, "c" : 3}; | |
| 147 var list = [1, 2, 3]; | |
| 148 var set = new Set.from(list); | |
| 149 var m = keysAndValues(map); | |
| 150 var l = keysAndValues(list); | |
| 151 var s = keysAndValues(set); | |
| 152 | |
| 153 m.forEach((key, value) {expect(key.charCodes[0], value + 96);}); | |
| 154 l.forEach((key, value) {expect(key + 1, value);}); | |
| 155 var index = 0; | |
| 156 var seen = new Set(); | |
| 157 s.forEach((key, value) { | |
| 158 expect(key, index++); | |
| 159 expect(seen.contains(value), isFalse); | |
| 160 seen.add(value); | |
| 161 }); | |
| 162 expect(seen.length, 3); | |
| 163 | |
| 164 var i = 0; | |
| 165 m = values(map); | |
| 166 l = values(list); | |
| 167 s = values(set); | |
| 168 m.forEach((each) {expect(each, ++i);}); | |
| 169 i = 0; | |
| 170 l.forEach((each) {expect(each, ++i);}); | |
| 171 i = 0; | |
| 172 s.forEach((each) {expect(each, ++i);}); | |
| 173 i = 0; | |
| 174 | |
| 175 seen = new Set(); | |
| 176 for (var each in m) { | |
| 177 expect(seen.contains(each), isFalse); | |
| 178 seen.add(each); | |
| 179 } | |
| 180 expect(seen.length, 3); | |
| 181 i = 0; | |
| 182 for (var each in l) { | |
| 183 expect(each, ++i); | |
| 184 } | |
| 185 }); | |
| 186 | |
| 187 Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3"); | |
| 188 n1.children = [n2, n3]; | |
| 189 n2.parent = n1; | |
| 190 n3.parent = n1; | |
| 191 | |
| 192 test('Trace a cyclical structure', () { | |
| 193 var s = new Serialization(); | |
| 194 var trace = new Trace(new Writer(s)); | |
| 195 trace.writer.trace = trace; | |
| 196 trace.trace(n1); | |
| 197 var all = trace.writer.references.keys; | |
| 198 expect(all.length, 4); | |
| 199 expect(all.contains(n1), isTrue); | |
| 200 expect(all.contains(n2), isTrue); | |
| 201 expect(all.contains(n3), isTrue); | |
| 202 expect(all.contains(n1.children), isTrue); | |
| 203 }); | |
| 204 | |
| 205 test('Flatten references in a cyclical structure', () { | |
| 206 var s = new Serialization(); | |
| 207 var w = new Writer(s); | |
| 208 w.trace = new Trace(w); | |
| 209 w.write(n1); | |
| 210 expect(w.states.length, 4); // prims, lists, essential lists, basic | |
| 211 var children = 0, name = 1, parent = 2; | |
| 212 List rootNode = w.states[3].filter((x) => x[name] == "1"); | |
| 213 rootNode = rootNode.first; | |
| 214 expect(rootNode[parent], isNull); | |
| 215 var list = w.states[1].first; | |
| 216 expect(w.stateForReference(rootNode[children]), list); | |
| 217 var parentNode = w.stateForReference(list[0])[parent]; | |
| 218 expect(w.stateForReference(parentNode), rootNode); | |
| 219 }); | |
| 220 | |
| 221 test('round-trip', () { | |
| 222 runRoundTripTest(nodeSerializerReflective); | |
| 223 }); | |
| 224 | |
| 225 test('round-trip hard-coded', () { | |
| 226 runRoundTripTest(nodeSerializerNonReflective); | |
| 227 }); | |
| 228 | |
| 229 test('round-trip with essential parent', () { | |
| 230 runRoundTripTest(nodeSerializerWithEssentialParent); | |
| 231 }); | |
| 232 | |
| 233 test('round-trip, flat format', () { | |
| 234 runRoundTripTestFlat(nodeSerializerReflective); | |
| 235 }); | |
| 236 | |
| 237 test('round-trip using Maps', () { | |
| 238 runRoundTripTest(nodeSerializerUsingMaps); | |
| 239 }); | |
| 240 | |
| 241 test('eating your own tail', () { | |
| 242 // Create a meta-serializer, that serializes serializations, then | |
| 243 // use it to serialize a basic serialization, then run a test on the | |
| 244 // the result. | |
| 245 var s = new Serialization() | |
| 246 ..addRuleFor(new Node(''), constructorFields: ['name']) | |
| 247 ..selfDescribing = false; | |
| 248 var meta = metaSerialization(); | |
| 249 var serialized = meta.write(s); | |
| 250 var s2 = new Reader(meta) | |
| 251 .readOne(serialized, {"Node" : reflect(new Node('')).type}); | |
| 252 runRoundTripTest((x) => s2); | |
| 253 }); | |
| 254 | |
| 255 test("Verify we're not serializing lists twice if they're essential", () { | |
| 256 Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3"); | |
| 257 n1.children = [n2, n3]; | |
| 258 n2.parent = n1; | |
| 259 n3.parent = n1; | |
| 260 var s = new Serialization() | |
| 261 ..addRuleFor(n1, constructorFields: ["name"]). | |
| 262 specialTreatmentFor("children", (parent, child) => | |
| 263 parent.reflectee.children = child); | |
| 264 var w = new Writer(s); | |
| 265 w.write(n1); | |
| 266 expect(w.rules[2] is ListRuleEssential, isTrue); | |
| 267 expect(w.rules[1] is ListRule, isTrue); | |
| 268 expect(w.states[1].length, 0); | |
| 269 expect(w.states[2].length, 1); | |
| 270 s = new Serialization() | |
| 271 ..addRuleFor(n1, constructorFields: ["name"]); | |
| 272 w = new Writer(s); | |
| 273 w.write(n1); | |
| 274 expect(w.states[1].length, 1); | |
| 275 expect(w.states[2].length, 0); | |
| 276 }); | |
| 277 | |
| 278 test('Identity of equal objects preserved', () { | |
| 279 Node n1 = new NodeEqualByName("foo"), | |
| 280 n2 = new NodeEqualByName("foo"), | |
| 281 n3 = new NodeEqualByName("3"); | |
| 282 n1.children = [n2, n3]; | |
| 283 n2.parent = n1; | |
| 284 n3.parent = n1; | |
| 285 var s = new Serialization() | |
| 286 ..selfDescribing = false | |
| 287 ..addRuleFor(n1, constructorFields: ["name"]); | |
| 288 var w = new Writer(s); | |
| 289 var r = new Reader(s); | |
| 290 var m1 = r.read(w.write(n1)).first; | |
| 291 var m2 = m1.children.first; | |
| 292 var m3 = m1.children.last; | |
| 293 expect(m1, m2); | |
| 294 expect(identical(m1, m2), isFalse); | |
| 295 expect(m1 == m3, isFalse); | |
| 296 expect(identical(m2.parent, m3.parent), isTrue); | |
| 297 }); | |
| 298 | |
| 299 } | |
| 300 | |
| 301 /****************************************************************************** | |
| 302 * The end of the tests and the beginning of various helper functions to make | |
| 303 * it easier to write the repetitive sections. | |
| 304 ******************************************************************************/ | |
| 305 | |
| 306 /** Create a Serialization for serializing Serializations. */ | |
| 307 Serialization metaSerialization() { | |
| 308 // Make some bogus rule instances so we have something to feed rule creation | |
| 309 // and get their types. If only we had class literals implemented... | |
| 310 var closureRule = new ClosureToMapRule.stub([].runtimeType); | |
| 311 var basicRule = new BasicRule(reflect(null).type, '', [], [], []); | |
| 312 | |
| 313 var meta = new Serialization() | |
| 314 ..selfDescribing = false | |
| 315 ..addRuleFor(new ListRule()) | |
| 316 ..addRuleFor(new PrimitiveRule()) | |
| 317 // TODO(alanknight): Handle the ClosureToMapRule as well. | |
| 318 // Note that we're passing in a constant for one of the fields. | |
| 319 ..addRuleFor(basicRule, | |
| 320 constructorFields: ['typeWrapped', | |
| 321 'constructorName', | |
| 322 'constructorFields', 'regularFields', []], | |
| 323 fields: []) | |
| 324 ..addRuleFor(new Serialization()).specialTreatmentFor('rules', | |
| 325 (InstanceMirror s, List rules) { | |
| 326 rules.forEach((x) => s.reflectee.addRule(x)); | |
| 327 }) | |
| 328 ..addRule(new ClassMirrorRule()); | |
| 329 return meta; | |
| 330 } | |
| 331 | |
| 332 /** | |
| 333 * Read back a simple object, assumed to be the only one of its class in the | |
| 334 * reader. | |
| 335 */ | |
| 336 readBackSimple(Serialization s, object, Reader reader) { | |
| 337 var rule = s.rulesFor(object)[0]; | |
| 338 reader.inflateForRule(rule); | |
| 339 var list2 = reader.allObjectsForRule(rule)[0]; | |
| 340 return list2; | |
| 341 } | |
| 342 | |
| 343 /** | |
| 344 * Set up a basic reader with some fake data. Hard-codes the assumption | |
| 345 * of how many rules there are. | |
| 346 */ | |
| 347 Reader setUpReader(aSerialization, sampleData) { | |
| 348 var reader = new Reader(aSerialization); | |
| 349 // We're not sure which rule needs the sample data, so put it everywhere | |
| 350 // and trust that the extra will just be ignored. | |
| 351 reader.data = [[sampleData], [sampleData], [sampleData], [sampleData]]; | |
| 352 return reader; | |
| 353 } | |
| 354 | |
| 355 /** Return a serialization for Node objects, using a reflective rule. */ | |
| 356 Serialization nodeSerializerReflective(Node n) { | |
| 357 return new Serialization() | |
| 358 ..addRuleFor(n, constructorFields: ["name"]) | |
| 359 ..externalObjects['Node'] = reflect(new Node('')).type; | |
| 360 } | |
| 361 | |
| 362 /** | |
| 363 * Return a serialization for Node objects but using Maps for the internal | |
| 364 * representation rather than lists. | |
| 365 */ | |
| 366 Serialization nodeSerializerUsingMaps(Node n) { | |
| 367 return new Serialization() | |
| 368 ..addRuleFor(n, constructorFields: ["name"]).configureForMaps() | |
| 369 ..externalObjects['Node'] = reflect(new Node('')).type; | |
| 370 } | |
| 371 | |
| 372 /** | |
| 373 * Return a serialization for Node objects where the "parent" instance | |
| 374 * variable is considered essential state. | |
| 375 */ | |
| 376 Serialization nodeSerializerWithEssentialParent(Node n) { | |
| 377 var s = new Serialization() | |
| 378 ..addRuleFor( | |
| 379 n, | |
| 380 constructor: "parentEssential", | |
| 381 constructorFields: ["parent"]) | |
| 382 ..externalObjects['Node'] = reflect(new Node('')).type | |
| 383 ..selfDescribing = false; | |
| 384 | |
| 385 // Force the node rule to be first, in order to make a cycle which would | |
| 386 // not cause a problem if we handled the list first, because the list | |
| 387 // considers all of its state non-essential, thus breaking the cycle. | |
| 388 s.rules = append([s.rules.removeLast()], s.rules); | |
| 389 keysAndValues(s.rules).forEach((index, rule) => rule.number = index); | |
| 390 return s; | |
| 391 } | |
| 392 | |
| 393 /** Return a serialization for Node objects using a ClosureToMapRule. */ | |
| 394 Serialization nodeSerializerNonReflective(Node n) { | |
| 395 var rule = new ClosureToMapRule( | |
| 396 n.runtimeType, | |
| 397 (o) => {"name" : o.name, "children" : o.children, "parent" : o.parent}, | |
| 398 (map) => new Node(map["name"]), | |
| 399 (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.
| |
| 400 ..children = map["children"] | |
| 401 ..parent = map["parent"]; | |
| 402 }); | |
| 403 return new Serialization() | |
| 404 ..selfDescribing = false | |
| 405 ..addRule(rule); | |
| 406 } | |
| 407 | |
| 408 /** | |
| 409 * Run a round-trip test on a simple tree of nodes, using a serialization | |
| 410 * that's returned by the [serializerSetup] function. | |
| 411 */ | |
| 412 runRoundTripTest(Function serializerSetUp) { | |
| 413 Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3"); | |
| 414 n1.children = [n2, n3]; | |
| 415 n2.parent = n1; | |
| 416 n3.parent = n1; | |
| 417 var s = serializerSetUp(n1); | |
| 418 var output = s.write(n2); | |
| 419 var s2 = serializerSetUp(n1); | |
| 420 var reader = new Reader(s2); | |
| 421 var m2 = reader.readOne(output); | |
| 422 var m1 = m2.parent; | |
| 423 expect(m1 is Node, isTrue); | |
| 424 var children = m1.children; | |
| 425 expect(m1.name,"1"); | |
| 426 var m3 = m1.children.last; | |
| 427 expect(m2.name, "2"); | |
| 428 expect(m3.name, "3"); | |
| 429 expect(m2.parent, m1); | |
| 430 expect(m3.parent, m1); | |
| 431 expect(m1.parent, isNull); | |
| 432 } | |
| 433 | |
| 434 /** | |
| 435 * Run a round-trip test on a simple of nodes, but using the flat format | |
| 436 * rather than the maps. | |
| 437 */ | |
| 438 runRoundTripTestFlat(serializerSetUp) { | |
| 439 Node n1 = new Node("1"), n2 = new Node("2"), n3 = new Node("3"); | |
| 440 n1.children = [n2, n3]; | |
| 441 n2.parent = n1; | |
| 442 n3.parent = n1; | |
| 443 var s = serializerSetUp(n1); | |
| 444 var output = s.writeFlat(n2); | |
| 445 var s2 = serializerSetUp(n1); | |
| 446 var reader = new Reader(s2); | |
| 447 var m2 = reader.readFlat(output).first; | |
| 448 var m1 = m2.parent; | |
| 449 expect(m1 is Node, isTrue); | |
| 450 var children = m1.children; | |
| 451 expect(m1.name,"1"); | |
| 452 var m3 = m1.children.last; | |
| 453 expect(m2.name, "2"); | |
| 454 expect(m3.name, "3"); | |
| 455 expect(m2.parent, m1); | |
| 456 expect(m3.parent, m1); | |
| 457 expect(m1.parent, isNull); | |
| 458 } | |
| 459 | |
| 460 /** Extract the state from [object] using the rules in [s] and return it. */ | |
| 461 states(Object object, Serialization s) { | |
| 462 var rules = s.rulesFor(object); | |
| 463 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.
| |
| 464 } | |
| OLD | NEW |