• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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