Index: test/codegen/corelib/collection_to_string_test.dart |
diff --git a/test/codegen/corelib/collection_to_string_test.dart b/test/codegen/corelib/collection_to_string_test.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..67608ffbbaafbe8b9c1062d7c69b688a60b6b7b6 |
--- /dev/null |
+++ b/test/codegen/corelib/collection_to_string_test.dart |
@@ -0,0 +1,365 @@ |
+// 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; |
+ |
+Math.Random rand; |
+ |
+main() { |
+ rand = new Math.Random(); |
+ 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}) { |
+ 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 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); |
+} |