blob: 8a2674f5e1bcea8847b7d4e7199c31d1851b2071 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert' as convert;
/// A Dart representation of a `digest.json` file.
///
/// A single file is typically used to represent the results of a test suite,
/// or a series of tests that have been run with the same [dimensions]. For
/// example, "Impeller Unittests on Vulkan running on MacOS" might generate
/// a `digest.json` file.
///
/// Other tools (perhaps implemented in other languages, like C++) can use this
/// format to communicate with the `golden_tests_harvester` tool without
/// relying on the tool directly (or the Dart SDK).
final class Digests {
/// Creates a new instance of [Digests].
///
/// In practice, [Digests.parse] is typically used to create a new instance
/// from an existing `digest.json` file read into memory as string contents.
const Digests({
required this.dimensions,
required this.entries,
});
/// Parses a `digest.json` file from a string.
factory Digests.parse(String json) {
final Object? decoded = convert.json.decode(json);
if (decoded is! Map<String, Object?>) {
throw FormatException(
'Expected a JSON object as the root, but got $decoded',
json,
);
}
final Object? dimensions = decoded['dimensions'];
if (dimensions is! Map<String, Object?>) {
throw FormatException(
'Expected a JSON object "dimensions", but got ${dimensions.runtimeType}',
dimensions,
);
}
final Object? entries = decoded['entries'];
if (entries is! List<Object?>) {
throw FormatException(
'Expected a JSON list "entries", but got ${entries.runtimeType}',
entries,
);
}
// Now parse the entries.
return Digests(
dimensions: dimensions.map((String key, Object? value) {
if (value is! String) {
throw FormatException(
'Expected a JSON string for dimension "$key", but got ${value.runtimeType}',
value,
);
}
return MapEntry<String, String>(key, value);
}),
entries: List<DigestEntry>.unmodifiable(entries.map((Object? entry) {
if (entry is! Map<String, Object?>) {
throw FormatException(
'Expected a JSON object for an entry, but got ${entry.runtimeType}',
entry,
);
}
return DigestEntry(
filename: entry['filename']! as String,
width: entry['width']! as int,
height: entry['height']! as int,
maxDiffPixelsPercent: entry['maxDiffPixelsPercent']! as double,
maxColorDelta: entry['maxColorDelta']! as int,
);
})),
);
}
/// A key-value map of dimensions to provide to Skia Gold.
final Map<String, String> dimensions;
/// A list of test-run entries.
final List<DigestEntry> entries;
}
/// A single entry in a `digest.json` file.
///
/// Each entry is a test-run (or part of a test-run).
final class DigestEntry {
/// Creates a new instance of [DigestEntry].
const DigestEntry({
required this.filename,
required this.width,
required this.height,
required this.maxDiffPixelsPercent,
required this.maxColorDelta,
});
/// File path that is a direct sibling of the parsed `digest.json`.
final String filename;
/// Width of the image.
final int width;
/// Height of the image.
final int height;
/// Maximum percentage of different pixels.
///
/// Within Skia Gold, this is called `differentPixelsRate`.
final double maxDiffPixelsPercent;
/// Maximum color delta.
///
/// Within Skia Gold, this is called `pixelColorDelta`.
final int maxColorDelta;
}