Migrate Color.toString() test, improves equalsIgnoringHashCodes (#154934)

This migrates the last failing test for https://github.com/flutter/engine/pull/54981.  In order to effectively resolve that test I had to make `equalsIgnoringHashCodes` more usable by printing out the line that differs instead of just a huge blob of "expected" vs "actual.

## example
Here's the output after the change.

### test
```
  test('equalsIgnoringHashCodes - wrong line', () {
    expect(
      '1\n2\n3\n4\n5\n6\n7\n8\n9\n10',
      equalsIgnoringHashCodes('1\n2\n3\n4\n5\n6\na\n8\n9\n10'),
    );
  });
```
### output
```
Expected: normalized value matches
          '1\n'
            '2\n'
            '3\n'
            '4\n'
            '5\n'
            '6\n'
            'a\n'
            '8\n'
            '9\n'
            '10'
  Actual: '1\n'
            '2\n'
            '3\n'
            '4\n'
            '5\n'
            '6\n'
            '7\n'
            '8\n'
            '9\n'
            '10'
   Which: Lines 7 differed, expected: 
          'a'
          but got
          '7'
```
This commit is contained in:
gaaclarke 2024-09-11 10:34:58 -07:00 committed by GitHub
parent bcf1c5a243
commit b5ce70a760
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 83 additions and 91 deletions

View File

@ -212,13 +212,12 @@ void main() {
),
);
// The important lines below are the ones marked with "<----"
expect(tester.binding.renderView.toStringDeep(minLevel: DiagnosticLevel.info), equalsIgnoringHashCodes(
expect(tester.binding.renderView.toStringDeep(minLevel: DiagnosticLevel.info, wrapWidth: 600), equalsIgnoringHashCodes(
'_ReusableRenderView#00000\n'
' │ debug mode enabled - ${Platform.operatingSystem}\n'
' │ view size: Size(2400.0, 1800.0) (in physical pixels)\n'
' │ device pixel ratio: 3.0 (physical pixels per logical pixel)\n'
' │ configuration: BoxConstraints(w=800.0, h=600.0) at 3.0x (in\n'
' │ logical pixels)\n'
' │ configuration: BoxConstraints(w=800.0, h=600.0) at 3.0x (in logical pixels)\n'
' │\n'
' └─child: RenderRepaintBoundary#00000\n'
' │ needs compositing\n'
@ -227,8 +226,7 @@ void main() {
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five repaints)\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' │ needs compositing\n'
@ -236,10 +234,7 @@ void main() {
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ painter: null\n'
' │ foregroundPainter:\n'
' │ _GlowingOverscrollIndicatorPainter(_GlowController(color:\n'
' │ Color(0xffffffff), axis: vertical), _GlowController(color:\n'
' │ Color(0xffffffff), axis: vertical))\n'
' │ foregroundPainter: _GlowingOverscrollIndicatorPainter(_GlowController(color: ${const Color(0xffffffff)}, axis: vertical), _GlowController(color: ${const Color(0xffffffff)}, axis: vertical))\n'
' │\n'
' └─child: RenderRepaintBoundary#00000\n'
' │ needs compositing\n'
@ -248,8 +243,7 @@ void main() {
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five repaints)\n'
' │\n'
' └─child: _RenderScrollSemantics#00000\n'
' │ needs compositing\n'
@ -305,38 +299,20 @@ void main() {
' │ size: Size(800.0, 600.0)\n'
' │ axisDirection: down\n'
' │ crossAxisDirection: right\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 0.0, range:\n'
' │ 0.0..39400.0, viewport: 600.0, ScrollableState,\n'
' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics ->\n'
' │ RangeMaintainingScrollPhysics, IdleScrollActivity#00000,\n'
' │ ScrollDirection.idle)\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 0.0, range: 0.0..39400.0, viewport: 600.0, ScrollableState, AlwaysScrollableScrollPhysics -> ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#00000, ScrollDirection.idle)\n'
' │ anchor: 0.0\n'
' │\n'
' └─center child: RenderSliverPadding#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 600.0,\n'
' │ crossAxisExtent: 800.0, crossAxisDirection:\n'
' │ AxisDirection.right, viewportMainAxisExtent: 600.0,\n'
' │ remainingCacheExtent: 850.0, cacheOrigin: 0.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
' │ cacheExtent: 850.0)\n'
' │ constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0, cacheOrigin: 0.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent: 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true, cacheExtent: 850.0)\n'
' │ padding: EdgeInsets.zero\n'
' │ textDirection: ltr\n'
' │\n'
' └─child: RenderSliverFixedExtentList#00000 relayoutBoundary=up2\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 600.0,\n'
' │ crossAxisExtent: 800.0, crossAxisDirection:\n'
' │ AxisDirection.right, viewportMainAxisExtent: 600.0,\n'
' │ remainingCacheExtent: 850.0, cacheOrigin: 0.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
' │ cacheExtent: 850.0)\n'
' │ constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 0.0, precedingScrollExtent: 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 600.0, remainingCacheExtent: 850.0, cacheOrigin: 0.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent: 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true, cacheExtent: 850.0)\n'
' │ currently live children: 0 to 2\n'
' │\n'
' ├─child with index 0: RenderLimitedBox#00000\n'
@ -387,13 +363,12 @@ void main() {
const GlobalObjectKey<_LeafState>(3).currentState!.setKeepAlive(true);
await tester.drag(find.byType(ListView), const Offset(0.0, -1000.0));
await tester.pump();
expect(tester.binding.renderView.toStringDeep(minLevel: DiagnosticLevel.info), equalsIgnoringHashCodes(
expect(tester.binding.renderView.toStringDeep(minLevel: DiagnosticLevel.info, wrapWidth: 600), equalsIgnoringHashCodes(
'_ReusableRenderView#00000\n'
' │ debug mode enabled - ${Platform.operatingSystem}\n'
' │ view size: Size(2400.0, 1800.0) (in physical pixels)\n'
' │ device pixel ratio: 3.0 (physical pixels per logical pixel)\n'
' │ configuration: BoxConstraints(w=800.0, h=600.0) at 3.0x (in\n'
' │ logical pixels)\n'
' │ configuration: BoxConstraints(w=800.0, h=600.0) at 3.0x (in logical pixels)\n'
' │\n'
' └─child: RenderRepaintBoundary#00000\n'
' │ needs compositing\n'
@ -402,8 +377,7 @@ void main() {
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five repaints)\n'
' │\n'
' └─child: RenderCustomPaint#00000\n'
' │ needs compositing\n'
@ -411,10 +385,7 @@ void main() {
' │ constraints: BoxConstraints(w=800.0, h=600.0)\n'
' │ size: Size(800.0, 600.0)\n'
' │ painter: null\n'
' │ foregroundPainter:\n'
' │ _GlowingOverscrollIndicatorPainter(_GlowController(color:\n'
' │ Color(0xffffffff), axis: vertical), _GlowController(color:\n'
' │ Color(0xffffffff), axis: vertical))\n'
' │ foregroundPainter: _GlowingOverscrollIndicatorPainter(_GlowController(color: ${const Color(0xffffffff)}, axis: vertical), _GlowController(color: ${const Color(0xffffffff)}, axis: vertical))\n'
' │\n'
' └─child: RenderRepaintBoundary#00000\n'
' │ needs compositing\n'
@ -423,8 +394,7 @@ void main() {
' │ layer: OffsetLayer#00000\n'
' │ size: Size(800.0, 600.0)\n'
' │ metrics: 0.0% useful (1 bad vs 0 good)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five\n'
' │ repaints)\n'
' │ diagnosis: insufficient data to draw conclusion (less than five repaints)\n'
' │\n'
' └─child: _RenderScrollSemantics#00000\n'
' │ needs compositing\n'
@ -480,38 +450,20 @@ void main() {
' │ size: Size(800.0, 600.0)\n'
' │ axisDirection: down\n'
' │ crossAxisDirection: right\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 2000.0,\n'
' │ range: 0.0..39400.0, viewport: 600.0, ScrollableState,\n'
' │ AlwaysScrollableScrollPhysics -> ClampingScrollPhysics ->\n'
' │ RangeMaintainingScrollPhysics, IdleScrollActivity#00000,\n'
' │ ScrollDirection.idle)\n'
' │ offset: ScrollPositionWithSingleContext#00000(offset: 2000.0, range: 0.0..39400.0, viewport: 600.0, ScrollableState, AlwaysScrollableScrollPhysics -> ClampingScrollPhysics -> RangeMaintainingScrollPhysics, IdleScrollActivity#00000, ScrollDirection.idle)\n'
' │ anchor: 0.0\n'
' │\n'
' └─center child: RenderSliverPadding#00000 relayoutBoundary=up1\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 2000.0, precedingScrollExtent: 0.0, remainingPaintExtent:\n'
' │ 600.0, crossAxisExtent: 800.0, crossAxisDirection:\n'
' │ AxisDirection.right, viewportMainAxisExtent: 600.0,\n'
' │ remainingCacheExtent: 1100.0, cacheOrigin: -250.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
' │ cacheExtent: 1100.0)\n'
' │ constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 2000.0, precedingScrollExtent: 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 600.0, remainingCacheExtent: 1100.0, cacheOrigin: -250.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent: 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true, cacheExtent: 1100.0)\n'
' │ padding: EdgeInsets.zero\n'
' │ textDirection: ltr\n'
' │\n'
' └─child: RenderSliverFixedExtentList#00000 relayoutBoundary=up2\n'
' │ parentData: paintOffset=Offset(0.0, 0.0) (can use size)\n'
' │ constraints: SliverConstraints(AxisDirection.down,\n'
' │ GrowthDirection.forward, ScrollDirection.idle, scrollOffset:\n'
' │ 2000.0, precedingScrollExtent: 0.0, remainingPaintExtent:\n'
' │ 600.0, crossAxisExtent: 800.0, crossAxisDirection:\n'
' │ AxisDirection.right, viewportMainAxisExtent: 600.0,\n'
' │ remainingCacheExtent: 1100.0, cacheOrigin: -250.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent:\n'
' │ 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true,\n'
' │ cacheExtent: 1100.0)\n'
' │ constraints: SliverConstraints(AxisDirection.down, GrowthDirection.forward, ScrollDirection.idle, scrollOffset: 2000.0, precedingScrollExtent: 0.0, remainingPaintExtent: 600.0, crossAxisExtent: 800.0, crossAxisDirection: AxisDirection.right, viewportMainAxisExtent: 600.0, remainingCacheExtent: 1100.0, cacheOrigin: -250.0)\n'
' │ geometry: SliverGeometry(scrollExtent: 40000.0, paintExtent: 600.0, maxPaintExtent: 40000.0, hasVisualOverflow: true, cacheExtent: 1100.0)\n'
' │ currently live children: 4 to 7\n'
' │\n'
' ├─child with index 4: RenderLimitedBox#00000 NEEDS-PAINT\n'

View File

@ -5,6 +5,7 @@
/// @docImport '_goldens_io.dart';
library;
import 'dart:convert' show LineSplitter;
import 'dart:math' as math;
import 'dart:ui' as ui;
@ -1198,19 +1199,24 @@ class _HasOneLineDescription extends Matcher {
}
class _EqualsIgnoringHashCodes extends Matcher {
_EqualsIgnoringHashCodes(Object v) : _value = _normalize(v);
_EqualsIgnoringHashCodes(Object v)
: _value = _normalize(v),
_stringValue = v is String ? _normalizeString(v) : null;
final Object _value;
final Iterable<String> _value;
final String? _stringValue;
static final Object _mismatchedValueKey = Object();
static final Object _lineNumberValueKey = Object();
static final Object _expectedLineValueKey = Object();
static final Object _seenLineValueKey = Object();
static String _normalizeString(String value) {
return value.replaceAll(RegExp(r'#[\da-fA-F]{5}'), '#00000');
}
static Object _normalize(Object value, {bool expected = true}) {
static Iterable<String> _normalize(Object value, {bool expected = true}) {
if (value is String) {
return _normalizeString(value);
return LineSplitter.split(value).map<String>((dynamic item) => _normalizeString(item.toString()));
}
if (value is Iterable<String>) {
return value.map<String>((dynamic item) => _normalizeString(item.toString()));
@ -1222,20 +1228,33 @@ class _EqualsIgnoringHashCodes extends Matcher {
@override
bool matches(dynamic object, Map<dynamic, dynamic> matchState) {
final Object normalized = _normalize(object as Object, expected: false);
if (!equals(_value).matches(normalized, matchState)) {
matchState[_mismatchedValueKey] = normalized;
return false;
final Iterable<String> normalized = _normalize(object as Object, expected: false);
final Iterator<String> expectedIt = _value.iterator;
final Iterator<String> seenIt = normalized.iterator;
int lineNumber = 1;
bool hasExpected = expectedIt.moveNext();
bool hasSeen = seenIt.moveNext();
while (hasExpected && hasSeen) {
if (!equals(expectedIt.current).matches(seenIt.current, matchState)) {
matchState[_lineNumberValueKey] = lineNumber;
matchState[_expectedLineValueKey] = expectedIt.current;
matchState[_seenLineValueKey] = seenIt.current;
return false;
}
lineNumber += 1;
hasExpected = expectedIt.moveNext();
hasSeen = seenIt.moveNext();
}
return true;
return !hasExpected && !hasSeen;
}
@override
Description describe(Description description) {
if (_value is String) {
return description.add('normalized value matches $_value');
}
return description.add('normalized value matches\n').addDescriptionOf(_value);
return description.add('normalized value matches\n').addDescriptionOf(_stringValue ?? _value);
}
@override
@ -1245,16 +1264,17 @@ class _EqualsIgnoringHashCodes extends Matcher {
Map<dynamic, dynamic> matchState,
bool verbose,
) {
if (matchState.containsKey(_mismatchedValueKey)) {
final Object actualValue = matchState[_mismatchedValueKey] as Object;
// Leading whitespace is added so that lines in the multiline
// description returned by addDescriptionOf are all indented equally
// which makes the output easier to read for this case.
return mismatchDescription
.add('was expected to be normalized value\n')
.addDescriptionOf(_value)
if (matchState.containsKey(_lineNumberValueKey) &&
matchState.containsKey(_expectedLineValueKey) &&
matchState.containsKey(_seenLineValueKey)) {
final int lineNumber = matchState[_lineNumberValueKey] as int;
if (lineNumber > 1) {
mismatchDescription = mismatchDescription
.add('Lines $lineNumber differed, expected: \n')
.addDescriptionOf(matchState[_expectedLineValueKey])
.add('\nbut got\n')
.addDescriptionOf(actualValue);
.addDescriptionOf(matchState[_seenLineValueKey]);
}
}
return mismatchDescription;
}

View File

@ -194,6 +194,26 @@ void main() {
);
});
test('equalsIgnoringHashCodes - wrong line', () {
TestFailure? failure;
try {
expect(
'1\n2\n3\n4\n5\n6\n7\n8\n9\n10',
equalsIgnoringHashCodes('1\n2\n3\n4\n5\n6\na\n8\n9\n10'),
);
} on TestFailure catch (e) {
failure = e;
}
expect(failure, isNotNull);
if (failure != null) {
final String? message = failure.message;
expect(message, contains('Lines 7 differed'));
expect(message, contains("'a'"));
expect(message, contains("'7'"));
}
});
test('moreOrLessEquals', () {
expect(0.0, moreOrLessEquals(1e-11));
expect(1e-11, moreOrLessEquals(0.0));