diff --git a/pkgs/checks/CHANGELOG.md b/pkgs/checks/CHANGELOG.md index 3aa11d050..834d180da 100644 --- a/pkgs/checks/CHANGELOG.md +++ b/pkgs/checks/CHANGELOG.md @@ -1,6 +1,7 @@ ## 0.3.2-wip - Require Dart 3.7 +- Improve speed of pretty printing for large collections. ## 0.3.1 @@ -13,7 +14,7 @@ combined functionality in `containsInOrder`. - Replace `pairwiseComparesTo` with `pairwiseMatches`. - Fix a bug where printing the result of a failed deep quality check would - fail with a `TypeError` when comparing large `Map` instances + fail with a `TypeError` when comparing large `Map` instances. - Increase SDK constraint to ^3.5.0. - Clarify this package is experimental. diff --git a/pkgs/checks/lib/src/describe.dart b/pkgs/checks/lib/src/describe.dart index 6700af6bc..4f74e43ea 100644 --- a/pkgs/checks/lib/src/describe.dart +++ b/pkgs/checks/lib/src/describe.dart @@ -42,7 +42,7 @@ Iterable _prettyPrint( open = '('; close = ')'; } - final elements = object.map(prettyPrintNested).toList(); + final elements = object.map(prettyPrintNested); return _prettyPrintCollection( open, close, @@ -50,16 +50,15 @@ Iterable _prettyPrint( _maxLineLength - indentSize, ); } else if (object is Map) { - final entries = - object.entries.map((entry) { - final key = prettyPrintNested(entry.key); - final value = prettyPrintNested(entry.value); - return [ - ...key.take(key.length - 1), - '${key.last}: ${value.first}', - ...value.skip(1), - ]; - }).toList(); + final entries = object.entries.map((entry) { + final key = prettyPrintNested(entry.key); + final value = prettyPrintNested(entry.value); + return [ + ...key.take(key.length - 1), + '${key.last}: ${value.first}', + ...value.skip(1), + ]; + }); return _prettyPrintCollection( '{', '}', @@ -86,15 +85,19 @@ Iterable _prettyPrint( Iterable _prettyPrintCollection( String open, String close, - List> elements, + Iterable> elements, int maxLength, ) { - if (elements.length > _maxItems) { - const ellipsisElement = [ - ['...'], - ]; - elements.replaceRange(_maxItems - 1, elements.length, ellipsisElement); + final trimmedElements = >[]; + for (var element in elements) { + if (trimmedElements.length >= _maxItems) { + trimmedElements[_maxItems - 1] = ['...']; + break; + } + trimmedElements.add(element); } + elements = trimmedElements; + if (elements.every((e) => e.length == 1)) { final singleLine = '$open${elements.map((e) => e.single).join(', ')}$close'; if (singleLine.length <= maxLength) { diff --git a/pkgs/checks/test/pretty_print_test.dart b/pkgs/checks/test/pretty_print_test.dart index 6998ab3cb..ece33b266 100644 --- a/pkgs/checks/test/pretty_print_test.dart +++ b/pkgs/checks/test/pretty_print_test.dart @@ -23,6 +23,12 @@ void main() { test('in iterables', () { check(literal(largeList.followedBy([]))).last.equals('...)'); }); + test('without processing truncated elements', () { + final poisonedList = [...largeList, _PoisonToString()]; + check(() { + literal(poisonedList); + }).returnsNormally(); + }); test('in maps', () { final map = Map.fromIterables(largeList, largeList); check(literal(map)).last.equals('...}'); @@ -30,3 +36,9 @@ void main() { }); }); } + +class _PoisonToString { + @override + String toString() => + throw StateError('Truncated entry should not be processed'); +} diff --git a/pkgs/matcher/CHANGELOG.md b/pkgs/matcher/CHANGELOG.md index ccac6f4a7..249b143a6 100644 --- a/pkgs/matcher/CHANGELOG.md +++ b/pkgs/matcher/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.12.19-wip + +* Improve speed of pretty printing for large collections. + ## 0.12.18 * Add `isSorted` and related matchers for iterables. diff --git a/pkgs/matcher/lib/src/pretty_print.dart b/pkgs/matcher/lib/src/pretty_print.dart index 0abd4a856..70f459609 100644 --- a/pkgs/matcher/lib/src/pretty_print.dart +++ b/pkgs/matcher/lib/src/pretty_print.dart @@ -39,9 +39,13 @@ String prettyPrint(Object? object, {int? maxLineLength, int? maxItems}) { var type = object is List ? '' : '${_typeName(object)}:'; // Truncate the list of strings if it's longer than [maxItems]. - var strings = object.map(pp).toList(); - if (maxItems != null && strings.length > maxItems) { - strings.replaceRange(maxItems - 1, strings.length, ['...']); + var strings = []; + for (var item in object) { + if (maxItems != null && strings.length >= maxItems) { + strings[maxItems - 1] = '...'; + break; + } + strings.add(pp(item)); } // If the printed string is short and doesn't contain a newline, print it @@ -58,15 +62,14 @@ String prettyPrint(Object? object, {int? maxLineLength, int? maxItems}) { return _indent(indent + 2) + string; }).join(',\n')}\n${_indent(indent)}]'; } else if (object is Map) { - // Convert the contents of the map to string representations. - var strings = - object.keys.map((key) { - return '${pp(key)}: ${pp(object[key])}'; - }).toList(); - // Truncate the list of strings if it's longer than [maxItems]. - if (maxItems != null && strings.length > maxItems) { - strings.replaceRange(maxItems - 1, strings.length, ['...']); + var strings = []; + for (var key in object.keys) { + if (maxItems != null && strings.length >= maxItems) { + strings[maxItems - 1] = '...'; + break; + } + strings.add('${pp(key)}: ${pp(object[key])}'); } // If the printed string is short and doesn't contain a newline, print it diff --git a/pkgs/matcher/pubspec.yaml b/pkgs/matcher/pubspec.yaml index 32bae77ee..f27ae6050 100644 --- a/pkgs/matcher/pubspec.yaml +++ b/pkgs/matcher/pubspec.yaml @@ -1,5 +1,5 @@ name: matcher -version: 0.12.18 +version: 0.12.19-wip description: >- Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.