1// Protocol Buffers - Google's data interchange format 2// Copyright 2008 Google Inc. All rights reserved. 3// https://developers.google.com/protocol-buffers/ 4// 5// Redistribution and use in source and binary forms, with or without 6// modification, are permitted provided that the following conditions are 7// met: 8// 9// * Redistributions of source code must retain the above copyright 10// notice, this list of conditions and the following disclaimer. 11// * Redistributions in binary form must reproduce the above 12// copyright notice, this list of conditions and the following disclaimer 13// in the documentation and/or other materials provided with the 14// distribution. 15// * Neither the name of Google Inc. nor the names of its 16// contributors may be used to endorse or promote products derived from 17// this software without specific prior written permission. 18// 19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31goog.require('goog.testing.asserts'); 32goog.require('goog.userAgent'); 33 34// CommonJS-LoadFromFile: testbinary_pb proto.jspb.test 35goog.require('proto.jspb.test.MapValueEnum'); 36goog.require('proto.jspb.test.MapValueMessage'); 37goog.require('proto.jspb.test.TestMapFields'); 38goog.require('proto.jspb.test.TestMapFieldsOptionalKeys'); 39goog.require('proto.jspb.test.TestMapFieldsOptionalValues'); 40goog.require('proto.jspb.test.MapEntryOptionalKeysStringKey'); 41goog.require('proto.jspb.test.MapEntryOptionalKeysInt32Key'); 42goog.require('proto.jspb.test.MapEntryOptionalKeysInt64Key'); 43goog.require('proto.jspb.test.MapEntryOptionalKeysBoolKey'); 44goog.require('proto.jspb.test.MapEntryOptionalValuesStringValue'); 45goog.require('proto.jspb.test.MapEntryOptionalValuesInt32Value'); 46goog.require('proto.jspb.test.MapEntryOptionalValuesInt64Value'); 47goog.require('proto.jspb.test.MapEntryOptionalValuesBoolValue'); 48goog.require('proto.jspb.test.MapEntryOptionalValuesDoubleValue'); 49goog.require('proto.jspb.test.MapEntryOptionalValuesEnumValue'); 50goog.require('proto.jspb.test.MapEntryOptionalValuesMessageValue'); 51 52// CommonJS-LoadFromFile: test_pb proto.jspb.test 53goog.require('proto.jspb.test.MapValueMessageNoBinary'); 54goog.require('proto.jspb.test.TestMapFieldsNoBinary'); 55 56/** 57 * Helper: check that the given map has exactly this set of (sorted) entries. 58 * @param {!jspb.Map} map 59 * @param {!Array<!Array<?>>} entries 60 */ 61function checkMapEquals(map, entries) { 62 var arr = map.toArray(); 63 assertEquals(arr.length, entries.length); 64 for (var i = 0; i < arr.length; i++) { 65 if (Array.isArray(arr[i])) { 66 assertTrue(Array.isArray(entries[i])); 67 assertArrayEquals(arr[i], entries[i]); 68 } else { 69 assertElementsEquals(arr[i], entries[i]); 70 } 71 } 72} 73 74/** 75 * Converts an ES6 iterator to an array. 76 * @template T 77 * @param {!Iterator<T>} iter an iterator 78 * @return {!Array<T>} 79 */ 80function toArray(iter) { 81 var arr = []; 82 while (true) { 83 var val = iter.next(); 84 if (val.done) { 85 break; 86 } 87 arr.push(val.value); 88 } 89 return arr; 90} 91 92 93/** 94 * Helper: generate test methods for this TestMapFields class. 95 * @param {?} msgInfo 96 * @param {?} submessageCtor 97 * @param {string} suffix 98 */ 99function makeTests(msgInfo, submessageCtor, suffix) { 100 /** 101 * Helper: fill all maps on a TestMapFields. 102 * @param {?} msg 103 */ 104 var fillMapFields = function(msg) { 105 msg.getMapStringStringMap().set('asdf', 'jkl;').set('key 2', 'hello world'); 106 msg.getMapStringInt32Map().set('a', 1).set('b', -2); 107 msg.getMapStringInt64Map().set('c', 0x100000000).set('d', 0x200000000); 108 msg.getMapStringBoolMap().set('e', true).set('f', false); 109 msg.getMapStringDoubleMap().set('g', 3.14159).set('h', 2.71828); 110 msg.getMapStringEnumMap() 111 .set('i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR) 112 .set('j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ); 113 msg.getMapStringMsgMap() 114 .set('k', new submessageCtor()) 115 .set('l', new submessageCtor()); 116 msg.getMapStringMsgMap().get('k').setFoo(42); 117 msg.getMapStringMsgMap().get('l').setFoo(84); 118 msg.getMapInt32StringMap().set(-1, 'a').set(42, 'b'); 119 msg.getMapInt64StringMap().set(0x123456789abc, 'c').set(0xcba987654321, 'd'); 120 msg.getMapBoolStringMap().set(false, 'e').set(true, 'f'); 121 }; 122 123 /** 124 * Helper: check all maps on a TestMapFields. 125 * @param {?} msg 126 */ 127 var checkMapFields = function(msg) { 128 checkMapEquals(msg.getMapStringStringMap(), [ 129 ['asdf', 'jkl;'], 130 ['key 2', 'hello world'] 131 ]); 132 checkMapEquals(msg.getMapStringInt32Map(), [ 133 ['a', 1], 134 ['b', -2] 135 ]); 136 checkMapEquals(msg.getMapStringInt64Map(), [ 137 ['c', 0x100000000], 138 ['d', 0x200000000] 139 ]); 140 checkMapEquals(msg.getMapStringBoolMap(), [ 141 ['e', true], 142 ['f', false] 143 ]); 144 checkMapEquals(msg.getMapStringDoubleMap(), [ 145 ['g', 3.14159], 146 ['h', 2.71828] 147 ]); 148 checkMapEquals(msg.getMapStringEnumMap(), [ 149 ['i', proto.jspb.test.MapValueEnum.MAP_VALUE_BAR], 150 ['j', proto.jspb.test.MapValueEnum.MAP_VALUE_BAZ] 151 ]); 152 checkMapEquals(msg.getMapInt32StringMap(), [ 153 [-1, 'a'], 154 [42, 'b'] 155 ]); 156 checkMapEquals(msg.getMapInt64StringMap(), [ 157 [0x123456789abc, 'c'], 158 [0xcba987654321, 'd'] 159 ]); 160 checkMapEquals(msg.getMapBoolStringMap(), [ 161 [false, 'e'], 162 [true, 'f'] 163 ]); 164 165 assertEquals(msg.getMapStringMsgMap().getLength(), 2); 166 assertEquals(msg.getMapStringMsgMap().get('k').getFoo(), 42); 167 assertEquals(msg.getMapStringMsgMap().get('l').getFoo(), 84); 168 169 var entries = toArray(msg.getMapStringMsgMap().entries()); 170 assertEquals(entries.length, 2); 171 entries.forEach(function(entry) { 172 var key = entry[0]; 173 var val = entry[1]; 174 assert(val === msg.getMapStringMsgMap().get(key)); 175 }); 176 177 msg.getMapStringMsgMap().forEach(function(val, key) { 178 assert(val === msg.getMapStringMsgMap().get(key)); 179 }); 180 }; 181 182 it('testMapStringStringField' + suffix, function() { 183 var msg = new msgInfo.constructor(); 184 assertEquals(msg.getMapStringStringMap().getLength(), 0); 185 assertEquals(msg.getMapStringInt32Map().getLength(), 0); 186 assertEquals(msg.getMapStringInt64Map().getLength(), 0); 187 assertEquals(msg.getMapStringBoolMap().getLength(), 0); 188 assertEquals(msg.getMapStringDoubleMap().getLength(), 0); 189 assertEquals(msg.getMapStringEnumMap().getLength(), 0); 190 assertEquals(msg.getMapStringMsgMap().getLength(), 0); 191 192 // Re-create to clear out any internally-cached wrappers, etc. 193 msg = new msgInfo.constructor(); 194 var m = msg.getMapStringStringMap(); 195 assertEquals(m.has('asdf'), false); 196 assertEquals(m.get('asdf'), undefined); 197 m.set('asdf', 'hello world'); 198 assertEquals(m.has('asdf'), true); 199 assertEquals(m.get('asdf'), 'hello world'); 200 m.set('jkl;', 'key 2'); 201 assertEquals(m.has('jkl;'), true); 202 assertEquals(m.get('jkl;'), 'key 2'); 203 assertEquals(m.getLength(), 2); 204 var it = m.entries(); 205 assertElementsEquals(it.next().value, ['asdf', 'hello world']); 206 assertElementsEquals(it.next().value, ['jkl;', 'key 2']); 207 assertEquals(it.next().done, true); 208 checkMapEquals(m, [ 209 ['asdf', 'hello world'], 210 ['jkl;', 'key 2'] 211 ]); 212 m.del('jkl;'); 213 assertEquals(m.has('jkl;'), false); 214 assertEquals(m.get('jkl;'), undefined); 215 assertEquals(m.getLength(), 1); 216 it = m.keys(); 217 assertEquals(it.next().value, 'asdf'); 218 assertEquals(it.next().done, true); 219 it = m.values(); 220 assertEquals(it.next().value, 'hello world'); 221 assertEquals(it.next().done, true); 222 223 var count = 0; 224 m.forEach(function(value, key, map) { 225 assertEquals(map, m); 226 assertEquals(key, 'asdf'); 227 assertEquals(value, 'hello world'); 228 count++; 229 }); 230 assertEquals(count, 1); 231 232 m.clear(); 233 assertEquals(m.getLength(), 0); 234 }); 235 236 237 /** 238 * Tests operations on maps with all key and value types. 239 */ 240 it('testAllMapTypes' + suffix, function() { 241 var msg = new msgInfo.constructor(); 242 fillMapFields(msg); 243 checkMapFields(msg); 244 }); 245 246 247 if (msgInfo.deserializeBinary) { 248 /** 249 * Tests serialization and deserialization in binary format. 250 */ 251 it('testBinaryFormat' + suffix, function() { 252 if (goog.userAgent.IE && !goog.userAgent.isDocumentModeOrHigher(10)) { 253 // IE8/9 currently doesn't support binary format because they lack 254 // TypedArray. 255 return; 256 } 257 258 // Check that the format is correct. 259 var msg = new msgInfo.constructor(); 260 msg.getMapStringStringMap().set('A', 'a'); 261 var serialized = msg.serializeBinary(); 262 var expectedSerialized = [ 263 0x0a, 0x6, // field 1 (map_string_string), delimited, length 6 264 0x0a, 0x1, // field 1 in submessage (key), delimited, length 1 265 0x41, // ASCII 'A' 266 0x12, 0x1, // field 2 in submessage (value), delimited, length 1 267 0x61 // ASCII 'a' 268 ]; 269 assertEquals(serialized.length, expectedSerialized.length); 270 for (var i = 0; i < serialized.length; i++) { 271 assertEquals(serialized[i], expectedSerialized[i]); 272 } 273 274 // Check that all map fields successfully round-trip. 275 msg = new msgInfo.constructor(); 276 fillMapFields(msg); 277 serialized = msg.serializeBinary(); 278 var decoded = msgInfo.deserializeBinary(serialized); 279 checkMapFields(decoded); 280 }); 281 282 /** 283 * Tests deserialization of undefined map keys go to default values in 284 * binary format. 285 */ 286 it('testMapDeserializationForUndefinedKeys', function() { 287 var testMessageOptionalKeys = new proto.jspb.test.TestMapFieldsOptionalKeys(); 288 var mapEntryStringKey = new proto.jspb.test.MapEntryOptionalKeysStringKey(); 289 mapEntryStringKey.setValue("a"); 290 testMessageOptionalKeys.setMapStringString(mapEntryStringKey); 291 var mapEntryInt32Key = new proto.jspb.test.MapEntryOptionalKeysInt32Key(); 292 mapEntryInt32Key.setValue("b"); 293 testMessageOptionalKeys.setMapInt32String(mapEntryInt32Key); 294 var mapEntryInt64Key = new proto.jspb.test.MapEntryOptionalKeysInt64Key(); 295 mapEntryInt64Key.setValue("c"); 296 testMessageOptionalKeys.setMapInt64String(mapEntryInt64Key); 297 var mapEntryBoolKey = new proto.jspb.test.MapEntryOptionalKeysBoolKey(); 298 mapEntryBoolKey.setValue("d"); 299 testMessageOptionalKeys.setMapBoolString(mapEntryBoolKey); 300 var deserializedMessage = msgInfo.deserializeBinary( 301 testMessageOptionalKeys.serializeBinary() 302 ); 303 checkMapEquals(deserializedMessage.getMapStringStringMap(), [ 304 ['', 'a'] 305 ]); 306 checkMapEquals(deserializedMessage.getMapInt32StringMap(), [ 307 [0, 'b'] 308 ]); 309 checkMapEquals(deserializedMessage.getMapInt64StringMap(), [ 310 [0, 'c'] 311 ]); 312 checkMapEquals(deserializedMessage.getMapBoolStringMap(), [ 313 [false, 'd'] 314 ]); 315 }); 316 317 /** 318 * Tests deserialization of undefined map values go to default values in 319 * binary format. 320 */ 321 it('testMapDeserializationForUndefinedValues', function() { 322 var testMessageOptionalValues = 323 new proto.jspb.test.TestMapFieldsOptionalValues(); 324 var mapEntryStringValue = 325 new proto.jspb.test.MapEntryOptionalValuesStringValue(); 326 mapEntryStringValue.setKey("a"); 327 testMessageOptionalValues.setMapStringString(mapEntryStringValue); 328 var mapEntryInt32Value = 329 new proto.jspb.test.MapEntryOptionalValuesInt32Value(); 330 mapEntryInt32Value.setKey("b"); 331 testMessageOptionalValues.setMapStringInt32(mapEntryInt32Value); 332 var mapEntryInt64Value = 333 new proto.jspb.test.MapEntryOptionalValuesInt64Value(); 334 mapEntryInt64Value.setKey("c"); 335 testMessageOptionalValues.setMapStringInt64(mapEntryInt64Value); 336 var mapEntryBoolValue = 337 new proto.jspb.test.MapEntryOptionalValuesBoolValue(); 338 mapEntryBoolValue.setKey("d"); 339 testMessageOptionalValues.setMapStringBool(mapEntryBoolValue); 340 var mapEntryDoubleValue = 341 new proto.jspb.test.MapEntryOptionalValuesDoubleValue(); 342 mapEntryDoubleValue.setKey("e"); 343 testMessageOptionalValues.setMapStringDouble(mapEntryDoubleValue); 344 var mapEntryEnumValue = 345 new proto.jspb.test.MapEntryOptionalValuesEnumValue(); 346 mapEntryEnumValue.setKey("f"); 347 testMessageOptionalValues.setMapStringEnum(mapEntryEnumValue); 348 var mapEntryMessageValue = 349 new proto.jspb.test.MapEntryOptionalValuesMessageValue(); 350 mapEntryMessageValue.setKey("g"); 351 testMessageOptionalValues.setMapStringMsg(mapEntryMessageValue); 352 var deserializedMessage = msgInfo.deserializeBinary( 353 testMessageOptionalValues.serializeBinary() 354 ); 355 checkMapEquals(deserializedMessage.getMapStringStringMap(), [ 356 ['a', ''] 357 ]); 358 checkMapEquals(deserializedMessage.getMapStringInt32Map(), [ 359 ['b', 0] 360 ]); 361 checkMapEquals(deserializedMessage.getMapStringInt64Map(), [ 362 ['c', 0] 363 ]); 364 checkMapEquals(deserializedMessage.getMapStringBoolMap(), [ 365 ['d', false] 366 ]); 367 checkMapEquals(deserializedMessage.getMapStringDoubleMap(), [ 368 ['e', 0.0] 369 ]); 370 checkMapEquals(deserializedMessage.getMapStringEnumMap(), [ 371 ['f', 0] 372 ]); 373 checkMapEquals(deserializedMessage.getMapStringMsgMap(), [ 374 ['g', []] 375 ]); 376 }); 377 } 378 379 380 /** 381 * Exercises the lazy map<->underlying array sync. 382 */ 383 it('testLazyMapSync' + suffix, function() { 384 // Start with a JSPB array containing a few map entries. 385 var entries = [ 386 ['a', 'entry 1'], 387 ['c', 'entry 2'], 388 ['b', 'entry 3'] 389 ]; 390 var msg = new msgInfo.constructor([entries]); 391 assertEquals(entries.length, 3); 392 assertEquals(entries[0][0], 'a'); 393 assertEquals(entries[1][0], 'c'); 394 assertEquals(entries[2][0], 'b'); 395 msg.getMapStringStringMap().del('a'); 396 assertEquals(entries.length, 3); // not yet sync'd 397 msg.toArray(); // force a sync 398 assertEquals(entries.length, 2); 399 assertEquals(entries[0][0], 'b'); // now in sorted order 400 assertEquals(entries[1][0], 'c'); 401 402 var a = msg.toArray(); 403 assertEquals(a[0], entries); // retains original reference 404 }); 405 406 /** 407 * Returns IteratorIterables for entries(), keys() and values(). 408 */ 409 it('testIteratorIterables' + suffix, function() { 410 var msg = new msgInfo.constructor(); 411 var m = msg.getMapStringStringMap(); 412 m.set('key1', 'value1'); 413 m.set('key2', 'value2'); 414 var entryIterator = m.entries(); 415 assertElementsEquals(entryIterator.next().value, ['key1', 'value1']); 416 assertElementsEquals(entryIterator.next().value, ['key2', 'value2']); 417 assertEquals(entryIterator.next().done, true); 418 419 try { 420 var entryIterable = m.entries()[Symbol.iterator](); 421 assertElementsEquals(entryIterable.next().value, ['key1', 'value1']); 422 assertElementsEquals(entryIterable.next().value, ['key2', 'value2']); 423 assertEquals(entryIterable.next().done, true); 424 } catch (err) { 425 // jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be 426 // undefined in some environment. 427 if (err.name != 'TypeError' && err.name != 'ReferenceError') { 428 throw err; 429 } 430 } 431 432 var keyIterator = m.keys(); 433 assertEquals(keyIterator.next().value, 'key1'); 434 assertEquals(keyIterator.next().value, 'key2'); 435 assertEquals(keyIterator.next().done, true); 436 437 try { 438 var keyIterable = m.keys()[Symbol.iterator](); 439 assertEquals(keyIterable.next().value, 'key1'); 440 assertEquals(keyIterable.next().value, 'key2'); 441 assertEquals(keyIterable.next().done, true); 442 } catch (err) { 443 // jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be 444 // undefined in some environment. 445 if (err.name != 'TypeError' && err.name != 'ReferenceError') { 446 throw err; 447 } 448 } 449 var valueIterator = m.values(); 450 assertEquals(valueIterator.next().value, 'value1'); 451 assertEquals(valueIterator.next().value, 'value2'); 452 assertEquals(valueIterator.next().done, true); 453 454 try { 455 var valueIterable = m.values()[Symbol.iterator](); 456 assertEquals(valueIterable.next().value, 'value1'); 457 assertEquals(valueIterable.next().value, 'value2'); 458 assertEquals(valueIterable.next().done, true); 459 } catch (err) { 460 // jspb.Map.ArrayIteratorIterable_.prototype[Symbol.iterator] may be 461 // undefined in some environment. 462 if (err.name != 'TypeError' && err.name != 'ReferenceError') { 463 throw err; 464 } 465 } 466 }); 467} 468 469describe('mapsTest', function() { 470 makeTests( 471 { 472 constructor: proto.jspb.test.TestMapFields, 473 deserializeBinary: proto.jspb.test.TestMapFields.deserializeBinary 474 }, 475 proto.jspb.test.MapValueMessage, '_Binary'); 476 makeTests( 477 { 478 constructor: proto.jspb.test.TestMapFieldsNoBinary, 479 deserializeBinary: null 480 }, 481 proto.jspb.test.MapValueMessageNoBinary, '_NoBinary'); 482}); 483