| 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 /** | |
| 6 * Tests for the toString methods on collections and maps. | |
| 7 */ | |
| 8 | |
| 9 library collection_to_string; | |
| 10 | |
| 11 import "package:expect/expect.dart"; | |
| 12 import 'dart:collection' show Queue, LinkedHashMap; | |
| 13 import 'dart:math' as Math; | |
| 14 | |
| 15 // TODO(jjb): seed random number generator when API allows it | |
| 16 | |
| 17 const int NUM_TESTS = 300; | |
| 18 const int MAX_COLLECTION_SIZE = 7; | |
| 19 | |
| 20 Math.Random rand; | |
| 21 | |
| 22 main() { | |
| 23 rand = new Math.Random(); | |
| 24 smokeTest(); | |
| 25 exactTest(); | |
| 26 inexactTest(); | |
| 27 } | |
| 28 | |
| 29 /** | |
| 30 * Test a few simple examples. | |
| 31 */ | |
| 32 void smokeTest() { | |
| 33 // Non-const lists | |
| 34 Expect.equals([].toString(), '[]'); | |
| 35 Expect.equals([1].toString(), '[1]'); | |
| 36 Expect.equals(['Elvis'].toString(), '[Elvis]'); | |
| 37 Expect.equals([null].toString(), '[null]'); | |
| 38 Expect.equals([1, 2].toString(), '[1, 2]'); | |
| 39 Expect.equals(['I', 'II'].toString(), '[I, II]'); | |
| 40 Expect.equals( | |
| 41 [ | |
| 42 [1, 2], | |
| 43 [3, 4], | |
| 44 [5, 6] | |
| 45 ].toString(), | |
| 46 '[[1, 2], [3, 4], [5, 6]]'); | |
| 47 | |
| 48 // Const lists | |
| 49 Expect.equals((const []).toString(), '[]'); | |
| 50 Expect.equals((const [1]).toString(), '[1]'); | |
| 51 Expect.equals((const ['Elvis']).toString(), '[Elvis]'); | |
| 52 Expect.equals((const [null]).toString(), '[null]'); | |
| 53 Expect.equals((const [1, 2]).toString(), '[1, 2]'); | |
| 54 Expect.equals((const ['I', 'II']).toString(), '[I, II]'); | |
| 55 Expect.equals( | |
| 56 (const [ | |
| 57 const [1, 2], | |
| 58 const [3, 4], | |
| 59 const [5, 6] | |
| 60 ]) | |
| 61 .toString(), | |
| 62 '[[1, 2], [3, 4], [5, 6]]'); | |
| 63 | |
| 64 // Non-const maps - Note that all keys are strings; the spec currently demands
this | |
| 65 Expect.equals({}.toString(), '{}'); | |
| 66 Expect.equals({'Elvis': 'King'}.toString(), '{Elvis: King}'); | |
| 67 Expect.equals({'Elvis': null}.toString(), '{Elvis: null}'); | |
| 68 Expect.equals({'I': 1, 'II': 2}.toString(), '{I: 1, II: 2}'); | |
| 69 Expect.equals( | |
| 70 { | |
| 71 'X': {'I': 1, 'II': 2}, | |
| 72 'Y': {'III': 3, 'IV': 4}, | |
| 73 'Z': {'V': 5, 'VI': 6} | |
| 74 }.toString(), | |
| 75 '{X: {I: 1, II: 2}, Y: {III: 3, IV: 4}, Z: {V: 5, VI: 6}}'); | |
| 76 | |
| 77 // Const maps | |
| 78 Expect.equals(const {}.toString(), '{}'); | |
| 79 Expect.equals(const {'Elvis': 'King'}.toString(), '{Elvis: King}'); | |
| 80 Expect.equals({'Elvis': null}.toString(), '{Elvis: null}'); | |
| 81 Expect.equals(const {'I': 1, 'II': 2}.toString(), '{I: 1, II: 2}'); | |
| 82 Expect.equals( | |
| 83 const { | |
| 84 'X': const {'I': 1, 'II': 2}, | |
| 85 'Y': const {'III': 3, 'IV': 4}, | |
| 86 'Z': const {'V': 5, 'VI': 6} | |
| 87 }.toString(), | |
| 88 '{X: {I: 1, II: 2}, Y: {III: 3, IV: 4}, Z: {V: 5, VI: 6}}'); | |
| 89 } | |
| 90 | |
| 91 // SERIOUS "BASHER" TESTS | |
| 92 | |
| 93 /** | |
| 94 * Generate a bunch of random collections (including Maps), and test that | |
| 95 * there string form is as expected. The collections include collections | |
| 96 * as elements, keys, and values, and include recursive references. | |
| 97 * | |
| 98 * This test restricts itself to collections with well-defined iteration | |
| 99 * orders (i.e., no HashSet, HashMap). | |
| 100 */ | |
| 101 void exactTest() { | |
| 102 for (int i = 0; i < NUM_TESTS; i++) { | |
| 103 // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes | |
| 104 int size = | |
| 105 Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); | |
| 106 | |
| 107 StringBuffer stringRep = new StringBuffer(); | |
| 108 Object o = randomCollection(size, stringRep, exact: true); | |
| 109 print(stringRep); | |
| 110 print(o); | |
| 111 Expect.equals(o.toString(), stringRep.toString()); | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 /** | |
| 116 * Generate a bunch of random collections (including Maps), and test that | |
| 117 * there string form is as expected. The collections include collections | |
| 118 * as elements, keys, and values, and include recursive references. | |
| 119 * | |
| 120 * This test includes collections with ill-defined iteration orders (i.e., | |
| 121 * HashSet, HashMap). As a consequence, it can't use equality tests on the | |
| 122 * string form. Instead, it performs equality tests on their "alphagrams." | |
| 123 * This might allow false positives, but it does give a fair amount of | |
| 124 * confidence. | |
| 125 */ | |
| 126 void inexactTest() { | |
| 127 for (int i = 0; i < NUM_TESTS; i++) { | |
| 128 // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes | |
| 129 int size = | |
| 130 Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); | |
| 131 | |
| 132 StringBuffer stringRep = new StringBuffer(); | |
| 133 Object o = randomCollection(size, stringRep, exact: false); | |
| 134 print(stringRep); | |
| 135 print(o); | |
| 136 Expect.equals(alphagram(o.toString()), alphagram(stringRep.toString())); | |
| 137 } | |
| 138 } | |
| 139 | |
| 140 /** | |
| 141 * Return a random collection (or Map) of the specified size, placing its | |
| 142 * string representation into the given string buffer. | |
| 143 * | |
| 144 * If exact is true, the returned collections will not be, and will not contain | |
| 145 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | |
| 146 */ | |
| 147 Object randomCollection(int size, StringBuffer stringRep, {bool exact}) { | |
| 148 return randomCollectionHelper(size, exact, stringRep, []); | |
| 149 } | |
| 150 | |
| 151 /** | |
| 152 * Return a random collection (or map) of the specified size, placing its | |
| 153 * string representation into the given string buffer. The beingMade | |
| 154 * parameter is a list of collections currently under construction, i.e., | |
| 155 * candidates for recursive references. | |
| 156 * | |
| 157 * If exact is true, the returned collections will not be, and will not contain | |
| 158 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | |
| 159 */ | |
| 160 Object randomCollectionHelper( | |
| 161 int size, bool exact, StringBuffer stringRep, List beingMade) { | |
| 162 double interfaceFrac = rand.nextDouble(); | |
| 163 | |
| 164 if (exact) { | |
| 165 if (interfaceFrac < 1 / 3) { | |
| 166 return randomList(size, exact, stringRep, beingMade); | |
| 167 } else if (interfaceFrac < 2 / 3) { | |
| 168 return randomQueue(size, exact, stringRep, beingMade); | |
| 169 } else { | |
| 170 return randomMap(size, exact, stringRep, beingMade); | |
| 171 } | |
| 172 } else { | |
| 173 if (interfaceFrac < 1 / 4) { | |
| 174 return randomList(size, exact, stringRep, beingMade); | |
| 175 } else if (interfaceFrac < 2 / 4) { | |
| 176 return randomQueue(size, exact, stringRep, beingMade); | |
| 177 } else if (interfaceFrac < 3 / 4) { | |
| 178 return randomSet(size, exact, stringRep, beingMade); | |
| 179 } else { | |
| 180 return randomMap(size, exact, stringRep, beingMade); | |
| 181 } | |
| 182 } | |
| 183 } | |
| 184 | |
| 185 /** | |
| 186 * Return a random List of the specified size, placing its string | |
| 187 * representation into the given string buffer. The beingMade | |
| 188 * parameter is a list of collections currently under construction, i.e., | |
| 189 * candidates for recursive references. | |
| 190 * | |
| 191 * If exact is true, the returned collections will not be, and will not contain | |
| 192 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | |
| 193 */ | |
| 194 List randomList(int size, bool exact, StringBuffer stringRep, List beingMade) { | |
| 195 return populateRandomCollection(size, exact, stringRep, beingMade, [], "[]"); | |
| 196 } | |
| 197 | |
| 198 /** | |
| 199 * Like randomList, but returns a queue. | |
| 200 */ | |
| 201 Queue randomQueue( | |
| 202 int size, bool exact, StringBuffer stringRep, List beingMade) { | |
| 203 return populateRandomCollection( | |
| 204 size, exact, stringRep, beingMade, new Queue(), "{}"); | |
| 205 } | |
| 206 | |
| 207 /** | |
| 208 * Like randomList, but returns a Set. | |
| 209 */ | |
| 210 Set randomSet(int size, bool exact, StringBuffer stringRep, List beingMade) { | |
| 211 // Until we have LinkedHashSet, method will only be called with exact==true | |
| 212 return populateRandomSet(size, exact, stringRep, beingMade, new Set()); | |
| 213 } | |
| 214 | |
| 215 /** | |
| 216 * Like randomList, but returns a map. | |
| 217 */ | |
| 218 Map randomMap(int size, bool exact, StringBuffer stringRep, List beingMade) { | |
| 219 if (exact) { | |
| 220 return populateRandomMap( | |
| 221 size, exact, stringRep, beingMade, new LinkedHashMap()); | |
| 222 } else { | |
| 223 return populateRandomMap(size, exact, stringRep, beingMade, | |
| 224 randomBool() ? new Map() : new LinkedHashMap()); | |
| 225 } | |
| 226 } | |
| 227 | |
| 228 /** | |
| 229 * Populates the given empty collection with elements, emitting the string | |
| 230 * representation of the collection to stringRep. The beingMade parameter is | |
| 231 * a list of collections currently under construction, i.e., candidates for | |
| 232 * recursive references. | |
| 233 * | |
| 234 * If exact is true, the elements of the returned collections will not be, | |
| 235 * and will not contain a collection with ill-defined iteration order | |
| 236 * (i.e., a HashSet or HashMap). | |
| 237 */ | |
| 238 populateRandomCollection(int size, bool exact, StringBuffer stringRep, | |
| 239 List beingMade, var coll, String delimiters) { | |
| 240 beingMade.add(coll); | |
| 241 int start = stringRep.length; | |
| 242 | |
| 243 stringRep.write(delimiters[0]); | |
| 244 | |
| 245 List indices = []; | |
| 246 for (int i = 0; i < size; i++) { | |
| 247 indices.add(stringRep.length); | |
| 248 if (i != 0) stringRep.write(', '); | |
| 249 coll.add(randomElement(random(size), exact, stringRep, beingMade)); | |
| 250 } | |
| 251 if (size > 5 && delimiters == "()") { | |
| 252 const int MAX_LENGTH = 80; | |
| 253 const int MIN_COUNT = 3; | |
| 254 const int MAX_COUNT = 100; | |
| 255 // It's an iterable, it may omit some elements. | |
| 256 int end = stringRep.length; | |
| 257 if (size > MAX_COUNT) { | |
| 258 // Last two elements are also omitted, just find the first three or | |
| 259 // first 60 characters. | |
| 260 for (int i = MIN_COUNT; i < size; i++) { | |
| 261 int startIndex = indices[i]; | |
| 262 if (startIndex - start > MAX_LENGTH - 6) { | |
| 263 // Limit - ", ...)".length. | |
| 264 String prefix = stringRep.toString().substring(0, startIndex); | |
| 265 stringRep.clear(); | |
| 266 stringRep.write(prefix); | |
| 267 stringRep.write(", ..."); | |
| 268 } | |
| 269 } | |
| 270 } else if (stringRep.length - start > MAX_LENGTH - 1) { | |
| 271 // 80 - ")".length. | |
| 272 // Last two elements are always included. Middle ones may be omitted. | |
| 273 int lastTwoLength = end - indices[indices.length - 2]; | |
| 274 // Try to find first element to omit. | |
| 275 for (int i = 3; i <= size - 3; i++) { | |
| 276 int elementEnd = indices[i + 1]; | |
| 277 int lengthAfter = elementEnd - start; | |
| 278 int ellipsisSize = 5; // ", ...".length | |
| 279 if (i == size - 3) ellipsisSize = 0; // No ellipsis if we hit the end. | |
| 280 if (lengthAfter + ellipsisSize + lastTwoLength > MAX_LENGTH - 1) { | |
| 281 // Omit this element and everything up to the last two. | |
| 282 int elementStart = indices[i]; | |
| 283 // Rewrite string buffer by copying it out, clearing, and putting | |
| 284 // the parts back in. | |
| 285 String buffer = stringRep.toString(); | |
| 286 String prefix = buffer.substring(0, elementStart); | |
| 287 String suffix = buffer.substring(end - lastTwoLength, end); | |
| 288 stringRep.clear(); | |
| 289 stringRep.write(prefix); | |
| 290 stringRep.write(", ..."); | |
| 291 stringRep.write(suffix); | |
| 292 break; | |
| 293 } | |
| 294 } | |
| 295 } | |
| 296 } | |
| 297 | |
| 298 stringRep.write(delimiters[1]); | |
| 299 beingMade.removeLast(); | |
| 300 return coll; | |
| 301 } | |
| 302 | |
| 303 /** Like populateRandomCollection, but for sets (elements must be hashable) */ | |
| 304 Set populateRandomSet( | |
| 305 int size, bool exact, StringBuffer stringRep, List beingMade, Set set) { | |
| 306 stringRep.write('{'); | |
| 307 | |
| 308 for (int i = 0; i < size; i++) { | |
| 309 if (i != 0) stringRep.write(', '); | |
| 310 set.add(i); | |
| 311 stringRep.write(i); | |
| 312 } | |
| 313 | |
| 314 stringRep.write('}'); | |
| 315 return set; | |
| 316 } | |
| 317 | |
| 318 /** Like populateRandomCollection, but for maps. */ | |
| 319 Map populateRandomMap( | |
| 320 int size, bool exact, StringBuffer stringRep, List beingMade, Map map) { | |
| 321 beingMade.add(map); | |
| 322 stringRep.write('{'); | |
| 323 | |
| 324 for (int i = 0; i < size; i++) { | |
| 325 if (i != 0) stringRep.write(', '); | |
| 326 | |
| 327 int key = i; // Ensures no duplicates | |
| 328 stringRep.write(key); | |
| 329 stringRep.write(': '); | |
| 330 Object val = randomElement(random(size), exact, stringRep, beingMade); | |
| 331 map[key] = val; | |
| 332 } | |
| 333 | |
| 334 stringRep.write('}'); | |
| 335 beingMade.removeLast(); | |
| 336 return map; | |
| 337 } | |
| 338 | |
| 339 /** | |
| 340 * Generates a random element which can be an int, a collection, or a map, | |
| 341 * and emits it to StringRep. The beingMade parameter is a list of collections | |
| 342 * currently under construction, i.e., candidates for recursive references. | |
| 343 * | |
| 344 * If exact is true, the returned element will not be, and will not contain | |
| 345 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | |
| 346 */ | |
| 347 Object randomElement( | |
| 348 int size, bool exact, StringBuffer stringRep, List beingMade) { | |
| 349 Object result; | |
| 350 double elementTypeFrac = rand.nextDouble(); | |
| 351 if (elementTypeFrac < 1 / 3) { | |
| 352 result = random(1000); | |
| 353 stringRep.write(result); | |
| 354 } else if (elementTypeFrac < 2 / 3) { | |
| 355 // Element is a random (new) collection | |
| 356 result = randomCollectionHelper(size, exact, stringRep, beingMade); | |
| 357 } else { | |
| 358 // Element is a random recursive ref | |
| 359 result = beingMade[random(beingMade.length)]; | |
| 360 if (result is List) { | |
| 361 stringRep.write('[...]'); | |
| 362 } else if (result is Set || result is Map || result is Queue) { | |
| 363 stringRep.write('{...}'); | |
| 364 } else { | |
| 365 stringRep.write('(...)'); | |
| 366 } | |
| 367 } | |
| 368 return result; | |
| 369 } | |
| 370 | |
| 371 /** Returns a random int on [0, max) */ | |
| 372 int random(int max) { | |
| 373 return rand.nextInt(max); | |
| 374 } | |
| 375 | |
| 376 /** Returns a random boolean value. */ | |
| 377 bool randomBool() { | |
| 378 return rand.nextBool(); | |
| 379 } | |
| 380 | |
| 381 /** Returns the alphabetized characters in a string. */ | |
| 382 String alphagram(String s) { | |
| 383 // Calling [toList] to convert unmodifiable list to normal list. | |
| 384 List<int> chars = s.codeUnits.toList(); | |
| 385 chars.sort((int a, int b) => a - b); | |
| 386 return new String.fromCharCodes(chars); | |
| 387 } | |
| OLD | NEW |