blob: 6005a4540a8b4517f9dfc458de1c4a00ba81d384 [file] [log] [blame]
// Copyright 2019 The Flutter Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd.
// ignore_for_file: avoid_print
import 'dart:io' as io;
import 'package:devtools_app/src/shared/diagnostics/diagnostics_node.dart';
import 'package:flutter_test/flutter_test.dart';
import '_golden_matcher_io.dart'
if (dart.library.js_interop) '_golden_matcher_web.dart'
as golden_matcher;
RemoteDiagnosticsNode? findNodeMatching(
RemoteDiagnosticsNode node,
String text,
) {
if (node.name?.startsWith(text) == true ||
node.description?.startsWith(text) == true) {
return node;
}
for (final child in node.childrenNow) {
final match = findNodeMatching(child, text);
if (match != null) {
return match;
}
}
return null;
}
String treeToDebugString(RemoteDiagnosticsNode node) {
return node.toDiagnosticsNode().toStringDeep();
}
String treeToDebugStringTruncated(RemoteDiagnosticsNode node, int maxLines) {
List<String> lines = node.toDiagnosticsNode().toStringDeep().split('\n');
if (lines.length > maxLines) {
lines = lines.take(maxLines).toList()..add('...');
}
return lines.join('\n');
}
/// Asserts that a [path] matches a golden file after normalizing likely hash
/// codes.
///
/// Paths are assumed to reference files within the `test/goldens` directory.
///
/// To rebaseline all golden files run:
/// ```
/// tool/update_goldens.sh
/// ```
///
/// A `#` followed by 5 hexadecimal digits is assumed to be a short hash code
/// and is normalized to #00000.
///
/// See Also:
///
/// * [equalsIgnoringHashCodes], which does the same thing without the golden
/// file functionality.
Matcher equalsGoldenIgnoringHashCodes(String path) {
return _EqualsGoldenIgnoringHashCodes(path);
}
class _EqualsGoldenIgnoringHashCodes extends Matcher {
_EqualsGoldenIgnoringHashCodes(String pathWithinGoldenDirectory) {
path = 'test/test_infra/goldens/$pathWithinGoldenDirectory';
try {
_value = _normalize(io.File(path).readAsStringSync());
} catch (e) {
_value = 'Error reading $path: $e';
}
}
late String path;
late String _value;
static final _mismatchedValueKey = Object();
static bool get updateGoldens => autoUpdateGoldenFiles;
static String _normalize(String s) {
return s.replaceAll(RegExp(r'#[0-9a-f]{5}'), '#00000');
}
@override
bool matches(Object? object, Map<dynamic, dynamic> matchState) {
final description = _normalize(object as String);
if (_value != description) {
if (updateGoldens) {
io.File(path).writeAsStringSync(description);
print('Updated golden file $path\nto\n$description');
// Act like the match succeeded so all goldens are updated instead of
// just the first failure.
return true;
}
matchState[_mismatchedValueKey] = description;
return false;
}
return true;
}
@override
Description describe(Description description) {
return description.add('multi line description equals $_value');
}
@override
Description describeMismatch(
Object? item,
Description mismatchDescription,
Map<dynamic, dynamic> matchState,
bool verbose,
) {
if (!matchState.containsKey(_mismatchedValueKey)) {
return mismatchDescription;
}
final String? actualValue = matchState[_mismatchedValueKey];
// Leading whitespace is added so that lines in the multi-line
// description returned by addDescriptionOf are all indented equally
// which makes the output easier to read for this case.
return mismatchDescription
.add('expected golden file \'$path\' with normalized value\n ')
.addDescriptionOf(_value)
.add('\nbut got\n ')
.addDescriptionOf(actualValue);
}
}
class AlwaysTrueMatcher extends Matcher {
const AlwaysTrueMatcher();
@override
bool matches(Object? object, Map<dynamic, dynamic> matchState) {
return true;
}
@override
Description describe(Description description) {
return description;
}
}
// TODO(https://github.com/flutter/devtools/issues/4060): add a check to the
// bots script that verifies we never use [matchesGoldenFile] directly.
/// A matcher for testing DevTools goldens which will always return true when
/// the platform is not MacOS.
///
/// This should always be used instead of [matchesGoldenFile] for testing
/// DevTools golden images.
Matcher matchesDevToolsGolden(Object key) {
return golden_matcher.matchesDevToolsGolden(key);
}