|  | // 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. | 
|  |  | 
|  | /** | 
|  | * Tests for the toString methods on collections and maps. | 
|  | */ | 
|  |  | 
|  | library collection_to_string; | 
|  |  | 
|  | import "package:expect/expect.dart"; | 
|  | import 'dart:collection' show Queue, LinkedHashMap; | 
|  | import 'dart:math' as Math; | 
|  |  | 
|  | // TODO(jjb): seed random number generator when API allows it | 
|  |  | 
|  | const int NUM_TESTS = 300; | 
|  | const int MAX_COLLECTION_SIZE = 7; | 
|  |  | 
|  | final rand = Math.Random(); | 
|  |  | 
|  | main() { | 
|  | smokeTest(); | 
|  | exactTest(); | 
|  | inexactTest(); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Test a few simple examples. | 
|  | */ | 
|  | void smokeTest() { | 
|  | // Non-const lists | 
|  | Expect.equals([].toString(), '[]'); | 
|  | Expect.equals([1].toString(), '[1]'); | 
|  | Expect.equals(['Elvis'].toString(), '[Elvis]'); | 
|  | Expect.equals([null].toString(), '[null]'); | 
|  | Expect.equals([1, 2].toString(), '[1, 2]'); | 
|  | Expect.equals(['I', 'II'].toString(), '[I, II]'); | 
|  | Expect.equals( | 
|  | [ | 
|  | [1, 2], | 
|  | [3, 4], | 
|  | [5, 6] | 
|  | ].toString(), | 
|  | '[[1, 2], [3, 4], [5, 6]]'); | 
|  |  | 
|  | // Const lists | 
|  | Expect.equals((const []).toString(), '[]'); | 
|  | Expect.equals((const [1]).toString(), '[1]'); | 
|  | Expect.equals((const ['Elvis']).toString(), '[Elvis]'); | 
|  | Expect.equals((const [null]).toString(), '[null]'); | 
|  | Expect.equals((const [1, 2]).toString(), '[1, 2]'); | 
|  | Expect.equals((const ['I', 'II']).toString(), '[I, II]'); | 
|  | Expect.equals( | 
|  | (const [ | 
|  | const [1, 2], | 
|  | const [3, 4], | 
|  | const [5, 6] | 
|  | ]) | 
|  | .toString(), | 
|  | '[[1, 2], [3, 4], [5, 6]]'); | 
|  |  | 
|  | // Non-const maps - Note that all keys are strings; the spec currently demands this | 
|  | Expect.equals({}.toString(), '{}'); | 
|  | Expect.equals({'Elvis': 'King'}.toString(), '{Elvis: King}'); | 
|  | Expect.equals({'Elvis': null}.toString(), '{Elvis: null}'); | 
|  | Expect.equals({'I': 1, 'II': 2}.toString(), '{I: 1, II: 2}'); | 
|  | Expect.equals( | 
|  | { | 
|  | 'X': {'I': 1, 'II': 2}, | 
|  | 'Y': {'III': 3, 'IV': 4}, | 
|  | 'Z': {'V': 5, 'VI': 6} | 
|  | }.toString(), | 
|  | '{X: {I: 1, II: 2}, Y: {III: 3, IV: 4}, Z: {V: 5, VI: 6}}'); | 
|  |  | 
|  | // Const maps | 
|  | Expect.equals(const {}.toString(), '{}'); | 
|  | Expect.equals(const {'Elvis': 'King'}.toString(), '{Elvis: King}'); | 
|  | Expect.equals({'Elvis': null}.toString(), '{Elvis: null}'); | 
|  | Expect.equals(const {'I': 1, 'II': 2}.toString(), '{I: 1, II: 2}'); | 
|  | Expect.equals( | 
|  | const { | 
|  | 'X': const {'I': 1, 'II': 2}, | 
|  | 'Y': const {'III': 3, 'IV': 4}, | 
|  | 'Z': const {'V': 5, 'VI': 6} | 
|  | }.toString(), | 
|  | '{X: {I: 1, II: 2}, Y: {III: 3, IV: 4}, Z: {V: 5, VI: 6}}'); | 
|  | } | 
|  |  | 
|  | // SERIOUS "BASHER" TESTS | 
|  |  | 
|  | /** | 
|  | * Generate a bunch of random collections (including Maps), and test that | 
|  | * there string form is as expected. The collections include collections | 
|  | * as elements, keys, and values, and include recursive references. | 
|  | * | 
|  | * This test restricts itself to collections with well-defined iteration | 
|  | * orders (i.e., no HashSet, HashMap). | 
|  | */ | 
|  | void exactTest() { | 
|  | for (int i = 0; i < NUM_TESTS; i++) { | 
|  | // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes | 
|  | int size = | 
|  | Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); | 
|  |  | 
|  | StringBuffer stringRep = new StringBuffer(); | 
|  | Object o = randomCollection(size, stringRep, exact: true); | 
|  | print(stringRep); | 
|  | print(o); | 
|  | Expect.equals(o.toString(), stringRep.toString()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Generate a bunch of random collections (including Maps), and test that | 
|  | * there string form is as expected. The collections include collections | 
|  | * as elements, keys, and values, and include recursive references. | 
|  | * | 
|  | * This test includes collections with ill-defined iteration orders (i.e., | 
|  | * HashSet, HashMap). As a consequence, it can't use equality tests on the | 
|  | * string form. Instead, it performs equality tests on their "alphagrams." | 
|  | * This might allow false positives, but it does give a fair amount of | 
|  | * confidence. | 
|  | */ | 
|  | void inexactTest() { | 
|  | for (int i = 0; i < NUM_TESTS; i++) { | 
|  | // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes | 
|  | int size = | 
|  | Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); | 
|  |  | 
|  | StringBuffer stringRep = new StringBuffer(); | 
|  | Object o = randomCollection(size, stringRep, exact: false); | 
|  | print(stringRep); | 
|  | print(o); | 
|  | Expect.equals(alphagram(o.toString()), alphagram(stringRep.toString())); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return a random collection (or Map) of the specified size, placing its | 
|  | * string representation into the given string buffer. | 
|  | * | 
|  | * If exact is true, the returned collections will not be, and will not contain | 
|  | * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | 
|  | */ | 
|  | Object randomCollection(int size, StringBuffer stringRep, {bool exact = false}) { | 
|  | return randomCollectionHelper(size, exact, stringRep, []); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return a random collection (or map) of the specified size, placing its | 
|  | * string representation into the given string buffer. The beingMade | 
|  | * parameter is a list of collections currently under construction, i.e., | 
|  | * candidates for recursive references. | 
|  | * | 
|  | * If exact is true, the returned collections will not be, and will not contain | 
|  | * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | 
|  | */ | 
|  | Object randomCollectionHelper( | 
|  | int size, bool exact, StringBuffer stringRep, List beingMade) { | 
|  | double interfaceFrac = rand.nextDouble(); | 
|  |  | 
|  | if (exact) { | 
|  | if (interfaceFrac < 1 / 3) { | 
|  | return randomList(size, exact, stringRep, beingMade); | 
|  | } else if (interfaceFrac < 2 / 3) { | 
|  | return randomQueue(size, exact, stringRep, beingMade); | 
|  | } else { | 
|  | return randomMap(size, exact, stringRep, beingMade); | 
|  | } | 
|  | } else { | 
|  | if (interfaceFrac < 1 / 4) { | 
|  | return randomList(size, exact, stringRep, beingMade); | 
|  | } else if (interfaceFrac < 2 / 4) { | 
|  | return randomQueue(size, exact, stringRep, beingMade); | 
|  | } else if (interfaceFrac < 3 / 4) { | 
|  | return randomSet(size, exact, stringRep, beingMade); | 
|  | } else { | 
|  | return randomMap(size, exact, stringRep, beingMade); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Return a random List of the specified size, placing its string | 
|  | * representation into the given string buffer. The beingMade | 
|  | * parameter is a list of collections currently under construction, i.e., | 
|  | * candidates for recursive references. | 
|  | * | 
|  | * If exact is true, the returned collections will not be, and will not contain | 
|  | * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | 
|  | */ | 
|  | List randomList(int size, bool exact, StringBuffer stringRep, List beingMade) { | 
|  | return populateRandomCollection(size, exact, stringRep, beingMade, [], "[]"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Like randomList, but returns a queue. | 
|  | */ | 
|  | Queue randomQueue( | 
|  | int size, bool exact, StringBuffer stringRep, List beingMade) { | 
|  | return populateRandomCollection( | 
|  | size, exact, stringRep, beingMade, new Queue(), "{}"); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Like randomList, but returns a Set. | 
|  | */ | 
|  | Set randomSet(int size, bool exact, StringBuffer stringRep, List beingMade) { | 
|  | // Until we have LinkedHashSet, method will only be called with exact==true | 
|  | return populateRandomSet(size, exact, stringRep, beingMade, new Set()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Like randomList, but returns a map. | 
|  | */ | 
|  | Map randomMap(int size, bool exact, StringBuffer stringRep, List beingMade) { | 
|  | if (exact) { | 
|  | return populateRandomMap( | 
|  | size, exact, stringRep, beingMade, new LinkedHashMap()); | 
|  | } else { | 
|  | return populateRandomMap(size, exact, stringRep, beingMade, | 
|  | randomBool() ? new Map() : new LinkedHashMap()); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Populates the given empty collection with elements, emitting the string | 
|  | * representation of the collection to stringRep.  The beingMade parameter is | 
|  | * a list of collections currently under construction, i.e., candidates for | 
|  | * recursive references. | 
|  | * | 
|  | * If exact is true, the elements of the returned collections will not be, | 
|  | * and will not contain a collection with ill-defined iteration order | 
|  | * (i.e., a HashSet or HashMap). | 
|  | */ | 
|  | populateRandomCollection(int size, bool exact, StringBuffer stringRep, | 
|  | List beingMade, var coll, String delimiters) { | 
|  | beingMade.add(coll); | 
|  | int start = stringRep.length; | 
|  |  | 
|  | stringRep.write(delimiters[0]); | 
|  |  | 
|  | List<int> indices = []; | 
|  | for (int i = 0; i < size; i++) { | 
|  | indices.add(stringRep.length); | 
|  | if (i != 0) stringRep.write(', '); | 
|  | coll.add(randomElement(random(size), exact, stringRep, beingMade)); | 
|  | } | 
|  | if (size > 5 && delimiters == "()") { | 
|  | const int MAX_LENGTH = 80; | 
|  | const int MIN_COUNT = 3; | 
|  | const int MAX_COUNT = 100; | 
|  | // It's an iterable, it may omit some elements. | 
|  | int end = stringRep.length; | 
|  | if (size > MAX_COUNT) { | 
|  | // Last two elements are also omitted, just find the first three or | 
|  | // first 60 characters. | 
|  | for (int i = MIN_COUNT; i < size; i++) { | 
|  | int startIndex = indices[i]; | 
|  | if (startIndex - start > MAX_LENGTH - 6) { | 
|  | // Limit - ", ...)".length. | 
|  | String prefix = stringRep.toString().substring(0, startIndex); | 
|  | stringRep.clear(); | 
|  | stringRep.write(prefix); | 
|  | stringRep.write(", ..."); | 
|  | } | 
|  | } | 
|  | } else if (stringRep.length - start > MAX_LENGTH - 1) { | 
|  | // 80 - ")".length. | 
|  | // Last two elements are always included. Middle ones may be omitted. | 
|  | int lastTwoLength = end - indices[indices.length - 2]; | 
|  | // Try to find first element to omit. | 
|  | for (int i = 3; i <= size - 3; i++) { | 
|  | int elementEnd = indices[i + 1]; | 
|  | int lengthAfter = elementEnd - start; | 
|  | int ellipsisSize = 5; // ", ...".length | 
|  | if (i == size - 3) ellipsisSize = 0; // No ellipsis if we hit the end. | 
|  | if (lengthAfter + ellipsisSize + lastTwoLength > MAX_LENGTH - 1) { | 
|  | // Omit this element and everything up to the last two. | 
|  | int elementStart = indices[i]; | 
|  | // Rewrite string buffer by copying it out, clearing, and putting | 
|  | // the parts back in. | 
|  | String buffer = stringRep.toString(); | 
|  | String prefix = buffer.substring(0, elementStart); | 
|  | String suffix = buffer.substring(end - lastTwoLength, end); | 
|  | stringRep.clear(); | 
|  | stringRep.write(prefix); | 
|  | stringRep.write(", ..."); | 
|  | stringRep.write(suffix); | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | stringRep.write(delimiters[1]); | 
|  | beingMade.removeLast(); | 
|  | return coll; | 
|  | } | 
|  |  | 
|  | /** Like populateRandomCollection, but for sets (elements must be hashable) */ | 
|  | Set populateRandomSet( | 
|  | int size, bool exact, StringBuffer stringRep, List beingMade, Set set) { | 
|  | stringRep.write('{'); | 
|  |  | 
|  | for (int i = 0; i < size; i++) { | 
|  | if (i != 0) stringRep.write(', '); | 
|  | set.add(i); | 
|  | stringRep.write(i); | 
|  | } | 
|  |  | 
|  | stringRep.write('}'); | 
|  | return set; | 
|  | } | 
|  |  | 
|  | /** Like populateRandomCollection, but for maps. */ | 
|  | Map populateRandomMap( | 
|  | int size, bool exact, StringBuffer stringRep, List beingMade, Map map) { | 
|  | beingMade.add(map); | 
|  | stringRep.write('{'); | 
|  |  | 
|  | for (int i = 0; i < size; i++) { | 
|  | if (i != 0) stringRep.write(', '); | 
|  |  | 
|  | int key = i; // Ensures no duplicates | 
|  | stringRep.write(key); | 
|  | stringRep.write(': '); | 
|  | Object val = randomElement(random(size), exact, stringRep, beingMade); | 
|  | map[key] = val; | 
|  | } | 
|  |  | 
|  | stringRep.write('}'); | 
|  | beingMade.removeLast(); | 
|  | return map; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Generates a random element which can be an int, a collection, or a map, | 
|  | * and emits it to StringRep. The beingMade parameter is a list of collections | 
|  | * currently under construction, i.e., candidates for recursive references. | 
|  | * | 
|  | * If exact is true, the returned element will not be, and will not contain | 
|  | * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | 
|  | */ | 
|  | Object randomElement( | 
|  | int size, bool exact, StringBuffer stringRep, List beingMade) { | 
|  | Object result; | 
|  | double elementTypeFrac = rand.nextDouble(); | 
|  | if (elementTypeFrac < 1 / 3) { | 
|  | result = random(1000); | 
|  | stringRep.write(result); | 
|  | } else if (elementTypeFrac < 2 / 3) { | 
|  | // Element is a random (new) collection | 
|  | result = randomCollectionHelper(size, exact, stringRep, beingMade); | 
|  | } else { | 
|  | // Element is a random recursive ref | 
|  | result = beingMade[random(beingMade.length)]; | 
|  | if (result is List) { | 
|  | stringRep.write('[...]'); | 
|  | } else if (result is Set || result is Map || result is Queue) { | 
|  | stringRep.write('{...}'); | 
|  | } else { | 
|  | stringRep.write('(...)'); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /** Returns a random int on [0, max) */ | 
|  | int random(int max) { | 
|  | return rand.nextInt(max); | 
|  | } | 
|  |  | 
|  | /** Returns a random boolean value. */ | 
|  | bool randomBool() { | 
|  | return rand.nextBool(); | 
|  | } | 
|  |  | 
|  | /** Returns the alphabetized characters in a string. */ | 
|  | String alphagram(String s) { | 
|  | // Calling [toList] to convert unmodifiable list to normal list. | 
|  | List<int> chars = s.codeUnits.toList(); | 
|  | chars.sort((int a, int b) => a - b); | 
|  | return new String.fromCharCodes(chars); | 
|  | } |