blob: 1ec9827fa81f80cda732cebac145bce51f0c416c [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 'package:engine_build_configs/engine_build_configs.dart';
/// Builds a test [BuilderConfig].
///
/// Many tests will involve exactly one build configuration, or a small number
/// of build configurations. Instead of constructing these configurations ahead
/// of time, this builder is used to create them on-the-fly, with convenient
/// methods for setting up and cloning configurations.
///
/// This builder exists in order to avoid global fixtures in tests that do not
/// isolate elements of the test environment relevant to their test. Prior to
/// the builder, 100s of lines of static configuration were used to setup and
/// instrument tests across multiple files; instead, this builder is used to
/// precisely configure the test environment for each test.
///
/// See <https://github.com/flutter/flutter/issues/148420> for more information.
final class TestBuilderConfig {
final _builds = <Map<String, Object?>>[];
/// Appends a build to the configuration.
void addBuild({
required String name,
required TestDroneDimension dimension,
bool enableRbe = false,
bool? enableLto,
String description = 'A default description.',
String? targetDir,
(String, List<String>)? generatorTask,
(String, List<String>)? testTask,
}) {
_builds.add({
'archives': [],
'drone_dimensions': [
dimension._dimension,
],
'gclient_variables': <String, Object?>{},
'gn': [
if (enableRbe) '--rbe',
if (enableLto == false) '--no-lto',
],
'name': name,
'description': description,
'ninja': <String, Object?>{
if (targetDir case final targetDir?) ...{
'config': targetDir,
'targets': ['ninja_target'],
}
},
'tests': _testTask(testTask),
'generators': _generatorTask(generatorTask),
});
}
static List<Object?> _testTask((String, List<String>)? task) {
if (task == null) {
return [];
}
final (script, args) = task;
return [
{
'name': 'test_task',
'language': 'python',
'scripts': [script],
'parameters': args,
'contexts': ['context'],
},
];
}
static Map<String, Object?> _generatorTask((String, List<String>)? task) {
if (task == null) {
return {};
}
final (script, args) = task;
return {
'tasks': [
{
'name': 'generator_task',
'language': 'python',
'scripts': [script],
'parameters': args,
},
],
};
}
/// Copies the state of `this` as a new [TestBuilderConfig].
TestBuilderConfig clone() {
final clone = TestBuilderConfig();
clone._builds.addAll(_builds);
return clone;
}
/// Creates and returns a [BuilderConfig] capturing the current builder state.
///
/// [path] is the path to the configuration file that would be read from disk.
///
/// After creation, the builder state remains, and changes can be made to the
/// builder to create a new configuration without affecting the previous one
/// created.
BuilderConfig buildConfig({
required String path,
}) {
final config = BuilderConfig.fromJson(map: buildJson(), path: path);
if (config.check(path) case final errors when errors.isNotEmpty) {
throw StateError('Invalid configuration:\n${errors.join('\n')}');
}
return config;
}
/// Creates and returns the JSON serialized format of a [BuilderConfig].
///
/// Most of the time, use [build] instead of this method.
///
/// It is undefined behavior to mutate the returned map.
Map<String, Object?> buildJson() {
return {
'builds': _builds,
};
}
}
/// Fixed set of dimensions for [TestBuilderConfig.addBuild].
enum TestDroneDimension {
/// Runs on Linux.
linux('os=Linux'),
/// Runs on macOS.
mac('os=Mac-12'),
/// Runs on Windows.
win('os=Windows-11');
const TestDroneDimension(this._dimension);
final String _dimension;
}