OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * Tests for the toString methods on collections and maps. | 6 * Tests for the toString methods on collections and maps. |
7 */ | 7 */ |
8 | 8 |
9 library collection_to_string; | 9 library collection_to_string; |
10 | 10 |
11 import "package:expect/expect.dart"; | 11 import "package:expect/expect.dart"; |
12 import 'dart:collection' show Queue, LinkedHashMap; | 12 import 'dart:collection' show Queue, LinkedHashMap; |
13 import 'dart:math' as Math; | 13 import 'dart:math' as Math; |
14 | 14 |
15 // TODO(jjb): seed random number generator when API allows it | 15 // TODO(jjb): seed random number generator when API allows it |
16 | 16 |
17 const int NUM_TESTS = 300; | 17 const int NUM_TESTS = 300; |
18 const int MAX_COLLECTION_SIZE = 7; | 18 const int MAX_COLLECTION_SIZE = 7; |
19 | 19 |
20 Math.Random rand; | 20 Math.Random rand; |
21 | 21 |
22 main() { | 22 main() { |
23 rand = new Math.Random(); | 23 rand = new Math.Random(); |
24 smokeTest(); | 24 smokeTest(); |
25 exactTest(); | 25 exactTest(); |
| 26 inexactTest(); |
26 } | 27 } |
27 | 28 |
28 | 29 |
29 /** | 30 /** |
30 * Test a few simple examples. | 31 * Test a few simple examples. |
31 */ | 32 */ |
32 void smokeTest() { | 33 void smokeTest() { |
33 // Non-const lists | 34 // Non-const lists |
34 Expect.equals([].toString(), '[]'); | 35 Expect.equals([].toString(), '[]'); |
35 Expect.equals([1].toString(), '[1]'); | 36 Expect.equals([1].toString(), '[1]'); |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
78 * orders (i.e., no HashSet, HashMap). | 79 * orders (i.e., no HashSet, HashMap). |
79 */ | 80 */ |
80 void exactTest() { | 81 void exactTest() { |
81 for (int i = 0; i < NUM_TESTS; i++) { | 82 for (int i = 0; i < NUM_TESTS; i++) { |
82 // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes | 83 // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes |
83 int size = | 84 int size = |
84 Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); | 85 Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); |
85 | 86 |
86 StringBuffer stringRep = new StringBuffer(); | 87 StringBuffer stringRep = new StringBuffer(); |
87 Object o = randomCollection(size, stringRep, exact:true); | 88 Object o = randomCollection(size, stringRep, exact:true); |
88 String expected = stringRep.toString(); | 89 print(stringRep); |
89 String actual = o.toString(); | 90 print(o); |
90 print("Expect: $expected"); | 91 Expect.equals(o.toString(), stringRep.toString()); |
91 print("Actual: $actual"); | |
92 Expect.equals(expected, actual); | |
93 } | 92 } |
94 } | 93 } |
95 | 94 |
| 95 /** |
| 96 * Generate a bunch of random collections (including Maps), and test that |
| 97 * there string form is as expected. The collections include collections |
| 98 * as elements, keys, and values, and include recursive references. |
| 99 * |
| 100 * This test includes collections with ill-defined iteration orders (i.e., |
| 101 * HashSet, HashMap). As a consequence, it can't use equality tests on the |
| 102 * string form. Instead, it performs equality tests on their "alphagrams." |
| 103 * This might allow false positives, but it does give a fair amount of |
| 104 * confidence. |
| 105 */ |
| 106 void inexactTest() { |
| 107 for (int i = 0; i < NUM_TESTS; i++) { |
| 108 // Choose a size from 0 to MAX_COLLECTION_SIZE, favoring larger sizes |
| 109 int size = |
| 110 Math.sqrt(random(MAX_COLLECTION_SIZE * MAX_COLLECTION_SIZE)).toInt(); |
| 111 |
| 112 StringBuffer stringRep = new StringBuffer(); |
| 113 Object o = randomCollection(size, stringRep, exact:false); |
| 114 print(stringRep); |
| 115 print(o); |
| 116 Expect.equals(alphagram(o.toString()), alphagram(stringRep.toString())); |
| 117 } |
| 118 } |
| 119 |
96 /** | 120 /** |
97 * Return a random collection (or Map) of the specified size, placing its | 121 * Return a random collection (or Map) of the specified size, placing its |
98 * string representation into the given string buffer. | 122 * string representation into the given string buffer. |
99 * | 123 * |
100 * If exact is true, the returned collections will not be, and will not contain | 124 * If exact is true, the returned collections will not be, and will not contain |
101 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | 125 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). |
102 */ | 126 */ |
103 Object randomCollection(int size, StringBuffer stringRep, {bool exact}) { | 127 Object randomCollection(int size, StringBuffer stringRep, {bool exact}) { |
104 return randomCollectionHelper(size, exact, stringRep, []); | 128 return randomCollectionHelper(size, exact, stringRep, []); |
105 } | 129 } |
106 | 130 |
107 /** | 131 /** |
108 * Return a random collection (or map) of the specified size, placing its | 132 * Return a random collection (or map) of the specified size, placing its |
109 * string representation into the given string buffer. The beingMade | 133 * string representation into the given string buffer. The beingMade |
110 * parameter is a list of collections currently under construction, i.e., | 134 * parameter is a list of collections currently under construction, i.e., |
111 * candidates for recursive references. | 135 * candidates for recursive references. |
112 * | 136 * |
113 * If exact is true, the returned collections will not be, and will not contain | 137 * If exact is true, the returned collections will not be, and will not contain |
114 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). | 138 * a collection with ill-defined iteration order (i.e., a HashSet or HashMap). |
115 */ | 139 */ |
116 Object randomCollectionHelper(int size, bool exact, StringBuffer stringRep, | 140 Object randomCollectionHelper(int size, bool exact, StringBuffer stringRep, |
117 List beingMade) { | 141 List beingMade) { |
118 double interfaceFrac = rand.nextDouble(); | 142 double interfaceFrac = rand.nextDouble(); |
119 | 143 |
120 if (exact) { | 144 if (exact) { |
121 if (interfaceFrac < 1/3) { | 145 if (interfaceFrac < 1/3) { |
122 return randomList(size, exact, stringRep, beingMade); | 146 return randomList(size, exact, stringRep, beingMade); |
123 } else if (interfaceFrac < 2/3) { | 147 } else if (interfaceFrac < 2/3) { |
124 return randomQueue(size, exact, stringRep, beingMade); | 148 return randomQueue(size, exact, stringRep, beingMade); |
125 } else { | 149 } else { |
126 return randomMap(size, exact, stringRep, beingMade); | 150 return randomMap(size, exact, stringRep, beingMade); |
127 } | 151 } |
(...skipping 29 matching lines...) Expand all Loading... |
157 Queue randomQueue(int size, bool exact, StringBuffer stringRep, List beingMade){ | 181 Queue randomQueue(int size, bool exact, StringBuffer stringRep, List beingMade){ |
158 return populateRandomCollection( | 182 return populateRandomCollection( |
159 size, exact, stringRep, beingMade, new Queue(), "{}"); | 183 size, exact, stringRep, beingMade, new Queue(), "{}"); |
160 } | 184 } |
161 | 185 |
162 /** | 186 /** |
163 * Like randomList, but returns a Set. | 187 * Like randomList, but returns a Set. |
164 */ | 188 */ |
165 Set randomSet(int size, bool exact, StringBuffer stringRep, List beingMade) { | 189 Set randomSet(int size, bool exact, StringBuffer stringRep, List beingMade) { |
166 // Until we have LinkedHashSet, method will only be called with exact==true | 190 // Until we have LinkedHashSet, method will only be called with exact==true |
167 return populateRandomCollection( | 191 return populateRandomSet(size, exact, stringRep, beingMade, new Set()); |
168 size, exact, stringRep, beingMade, new Set(), "{}"); | |
169 } | 192 } |
170 | 193 |
171 /** | 194 /** |
172 * Like randomList, but returns a map. | 195 * Like randomList, but returns a map. |
173 */ | 196 */ |
174 Map randomMap(int size, bool exact, StringBuffer stringRep, List beingMade) { | 197 Map randomMap(int size, bool exact, StringBuffer stringRep, List beingMade) { |
175 if (exact) { | 198 if (exact) { |
176 return populateRandomMap(size, exact, stringRep, beingMade, | 199 return populateRandomMap(size, exact, stringRep, beingMade, |
177 new LinkedHashMap()); | 200 new LinkedHashMap()); |
178 } else { | 201 } else { |
179 return populateRandomMap(size, exact, stringRep, beingMade, | 202 return populateRandomMap(size, exact, stringRep, beingMade, |
180 randomBool() ? new Map() : new LinkedHashMap()); | 203 randomBool() ? new Map() : new LinkedHashMap()); |
181 } | 204 } |
182 } | 205 } |
183 | 206 |
184 /** | 207 /** |
185 * Populates the given empty collection with elements, emitting the string | 208 * Populates the given empty collection with elements, emitting the string |
186 * representation of the collection to stringRep. The beingMade parameter is | 209 * representation of the collection to stringRep. The beingMade parameter is |
187 * a list of collections currently under construction, i.e., candidates for | 210 * a list of collections currently under construction, i.e., candidates for |
188 * recursive references. | 211 * recursive references. |
189 * | 212 * |
190 * If exact is true, the elements of the returned collections will not be, | 213 * If exact is true, the elements of the returned collections will not be, |
191 * and will not contain, a collection with undefined iteration order | 214 * and will not contain a collection with ill-defined iteration order |
192 * (i.e., a HashSet or HashMap). | 215 * (i.e., a HashSet or HashMap). |
193 */ | 216 */ |
194 populateRandomCollection(int size, bool exact, | 217 populateRandomCollection(int size, bool exact, |
195 StringBuffer stringRep, List beingMade, var coll, String delimiters) { | 218 StringBuffer stringRep, List beingMade, var coll, String delimiters) { |
196 beingMade.add(coll); | 219 beingMade.add(coll); |
197 int start = stringRep.length; | 220 int start = stringRep.length; |
198 | 221 |
199 stringRep.write(delimiters[0]); | 222 stringRep.write(delimiters[0]); |
200 | 223 |
201 List indices = []; | 224 List indices = []; |
202 for (int i = 0; i < size; i++) { | 225 for (int i = 0; i < size; i++) { |
203 indices.add(stringRep.length); | 226 indices.add(stringRep.length); |
204 if (i != 0) stringRep.write(', '); | 227 if (i != 0) stringRep.write(', '); |
205 coll.add(randomElement(random(size), exact, stringRep, beingMade)); | 228 coll.add(randomElement(random(size), exact, stringRep, beingMade)); |
206 } | 229 } |
207 if (size > 5 && coll is! Map | 230 if (size > 5 && delimiters == "()") { |
208 // Lists don't yet use ListMixin or its toString. | |
209 // Remove this line when they do. | |
210 && coll is! List /// 01: ok | |
211 ) { | |
212 const int MAX_LENGTH = 80; | 231 const int MAX_LENGTH = 80; |
213 const int MIN_COUNT = 3; | 232 const int MIN_COUNT = 3; |
214 const int MAX_COUNT = 100; | 233 const int MAX_COUNT = 100; |
215 // It may omit some elements. | 234 // It's an iterable, it may omit some elements. |
216 int end = stringRep.length; | 235 int end = stringRep.length; |
217 if (size > MAX_COUNT) { | 236 if (size > MAX_COUNT) { |
218 // Last two elements are also omitted, just find the first three elements | 237 // Last two elements are also omitted, just find the first three or |
219 // or first 60 characters. | 238 // first 60 characters. |
220 for (int i = MIN_COUNT; i < size; i++) { | 239 for (int i = MIN_COUNT; i < size; i++) { |
221 int startIndex = indices[i]; | 240 int startIndex = indices[i]; |
222 if (startIndex - start > MAX_LENGTH - 6) { // Limit - ", ...)".length. | 241 if (startIndex - start > MAX_LENGTH - 6) { // Limit - ", ...)".length. |
223 String prefix = stringRep.toString().substring(0, startIndex); | 242 String prefix = stringRep.toString().substring(0, startIndex); |
224 stringRep.clear(); | 243 stringRep.clear(); |
225 stringRep.write(prefix); | 244 stringRep.write(prefix); |
226 stringRep.write(", ..."); | 245 stringRep.write(", ..."); |
227 } | 246 } |
228 } | 247 } |
229 } else if (stringRep.length - start > MAX_LENGTH - 1) { // 80 - ")".length. | 248 } else if (stringRep.length - start > MAX_LENGTH - 1) { // 80 - ")".length. |
230 // Last two elements are always included. Middle ones may be omitted. | 249 // Last two elements are always included. Middle ones may be omitted. |
231 int lastTwoLength = end - indices[indices.length - 2]; | 250 int lastTwoLength = end - indices[indices.length - 2]; |
232 // Try to find first element to omit. | 251 // Try to find first element to omit. |
233 for (int i = 3; i <= size - 3; i++) { | 252 for (int i = 3; i <= size - 3; i++) { |
234 int elementEnd = indices[i + 1]; | 253 int elementEnd = indices[i + 1]; |
235 int lengthAfter = elementEnd - start; | 254 int lengthAfter = elementEnd - start; |
236 int ellipsisSize = 5; // ", ...".length | 255 int ellipsisSize = 5; // ", ...".length |
237 if (i == size - 3) ellipsisSize = 0; // No ellipsis if we hit the end. | 256 if (i == size - 3) ellipsisSize = 0; // No ellipsis if we hit the end. |
238 if (lengthAfter + ellipsisSize + lastTwoLength > MAX_LENGTH - 1) { | 257 if (lengthAfter + ellipsisSize + lastTwoLength > MAX_LENGTH - 1) { |
239 // Omit this element and everything up to the last two. | 258 // Omit this element and everything up to the last two. |
240 int elementStart = indices[i]; | 259 int elementStart = indices[i]; |
241 if (elementStart + ellipsisSize + lastTwoLength >= stringRep.length) { | |
242 break; | |
243 } | |
244 // Rewrite string buffer by copying it out, clearing, and putting | 260 // Rewrite string buffer by copying it out, clearing, and putting |
245 // the parts back in. | 261 // the parts back in. |
246 String buffer = stringRep.toString(); | 262 String buffer = stringRep.toString(); |
247 String prefix = buffer.substring(0, elementStart); | 263 String prefix = buffer.substring(0, elementStart); |
248 String suffix = buffer.substring(end - lastTwoLength, end); | 264 String suffix = buffer.substring(end - lastTwoLength, end); |
249 stringRep.clear(); | 265 stringRep.clear(); |
250 stringRep.write(prefix); | 266 stringRep.write(prefix); |
251 stringRep.write(", ..."); | 267 stringRep.write(", ..."); |
252 stringRep.write(suffix); | 268 stringRep.write(suffix); |
253 break; | 269 break; |
254 } | 270 } |
255 } | 271 } |
256 } | 272 } |
257 } | 273 } |
258 | 274 |
259 stringRep.write(delimiters[1]); | 275 stringRep.write(delimiters[1]); |
260 beingMade.removeLast(); | 276 beingMade.removeLast(); |
261 return coll; | 277 return coll; |
262 } | 278 } |
263 | 279 |
| 280 /** Like populateRandomCollection, but for sets (elements must be hashable) */ |
| 281 Set populateRandomSet(int size, bool exact, StringBuffer stringRep, |
| 282 List beingMade, Set set) { |
| 283 stringRep.write('{'); |
| 284 |
| 285 for (int i = 0; i < size; i++) { |
| 286 if (i != 0) stringRep.write(', '); |
| 287 set.add(i); |
| 288 stringRep.write(i); |
| 289 } |
| 290 |
| 291 stringRep.write('}'); |
| 292 return set; |
| 293 } |
| 294 |
| 295 |
264 /** Like populateRandomCollection, but for maps. */ | 296 /** Like populateRandomCollection, but for maps. */ |
265 Map populateRandomMap(int size, bool exact, StringBuffer stringRep, | 297 Map populateRandomMap(int size, bool exact, StringBuffer stringRep, |
266 List beingMade, Map map) { | 298 List beingMade, Map map) { |
267 beingMade.add(map); | 299 beingMade.add(map); |
268 stringRep.write('{'); | 300 stringRep.write('{'); |
269 | 301 |
270 for (int i = 0; i < size; i++) { | 302 for (int i = 0; i < size; i++) { |
271 if (i != 0) stringRep.write(', '); | 303 if (i != 0) stringRep.write(', '); |
272 | 304 |
273 int key = i; // Ensures no duplicates | 305 int key = i; // Ensures no duplicates |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
316 | 348 |
317 /** Returns a random int on [0, max) */ | 349 /** Returns a random int on [0, max) */ |
318 int random(int max) { | 350 int random(int max) { |
319 return rand.nextInt(max); | 351 return rand.nextInt(max); |
320 } | 352 } |
321 | 353 |
322 /** Returns a random boolean value. */ | 354 /** Returns a random boolean value. */ |
323 bool randomBool() { | 355 bool randomBool() { |
324 return rand.nextBool(); | 356 return rand.nextBool(); |
325 } | 357 } |
| 358 |
| 359 /** Returns the alphabetized characters in a string. */ |
| 360 String alphagram(String s) { |
| 361 // Calling [toList] to convert unmodifiable list to normal list. |
| 362 List<int> chars = s.codeUnits.toList(); |
| 363 chars.sort((int a, int b) => a - b); |
| 364 return new String.fromCharCodes(chars); |
| 365 } |
OLD | NEW |