mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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:
parent
bcf1c5a243
commit
b5ce70a760
@ -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'
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user