989709f3a8
A previous change made these omit elements to keep the toString length down. That could break code that expects the original behavior. This still avoids using IterableMixinWorkaround. Add static toString methods on ListBase, SetBase, IterableBase so that users can get the same behavior as our toString methods, and with the same cycle detection safety. Unify all collection toString methods in two methods: - IterableBase.iterableToShortString - IterableBase.iterableToFullString R=floitsch@google.com Review URL: https://codereview.chromium.org//297053002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@36616 260f80e4-7a28-3924-810f-c04153c831b5
366 lines
12 KiB
Dart
366 lines
12 KiB
Dart
// 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);
|
|
}
|