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