blob: 691ddbb9a545b274b1215b752f2232e924464cac [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:args/args.dart';
import 'package:engine_build_configs/engine_build_configs.dart';
import 'package:engine_tool/src/build_plan.dart';
import 'package:engine_tool/src/environment.dart';
import 'package:engine_tool/src/logger.dart';
import 'package:test/test.dart';
import 'src/test_build_configs.dart';
import 'src/utils.dart';
void main() {
/// Configures and parses [BuildPlan] from [args].
///
/// By default a non-verbose, non-RBE, Linux x64 configured [BuildPlan]
/// is created and used to configure the available options and instructions
/// for the returned [ArgParser].
///
/// - To use a different [TestEnvironment], provide [environment].
/// - To use a different set of builds, provide [builds].
BuildPlan configureAndParse(
List<String> args, {
TestEnvironment? environment,
void Function(TestBuilderConfig)? builds,
}) {
final (builders, parser, env) = _createTestFixture(
environment: environment,
builds: builds,
);
return BuildPlan.fromArgResults(
parser.parse(args),
env,
builds: builders,
);
}
test('rbe defaults to true if detected', () {
final plan = configureAndParse(
[],
environment: TestEnvironment.withTestEngine(
withRbe: true,
),
);
expect(plan.useRbe, isTrue);
expect(plan.toGnArgs(), isNot(contains('--no-rbe')));
});
test('rbe defaults to false if not detected', () {
final plan = configureAndParse(
[],
environment: TestEnvironment.withTestEngine(
// This is the default, but make it explicit for the test.
// ignore: avoid_redundant_argument_values
withRbe: false,
),
);
expect(plan.useRbe, isFalse);
expect(plan.toGnArgs(), contains('--no-rbe'));
});
test('lto is true if explicitly enabled', () {
final plan = configureAndParse(['--lto']);
expect(plan.useLto, isTrue);
expect(plan.toGnArgs(), contains('--lto'));
});
test('lto is false if explicitly disabled', () {
final plan = configureAndParse(['--no-lto']);
expect(plan.useLto, isFalse);
expect(plan.toGnArgs(), contains('--no-lto'));
});
test('lto is true if the config omits --no-lto', () {
final plan = configureAndParse([]);
expect(
plan.useLto,
isTrue,
reason: 'Not specified and the build config did not include --no-lto',
);
expect(plan.toGnArgs(), contains('--lto'));
});
test('lto is false if the config uses --no-lto', () {
final plan = configureAndParse([], builds: (testConfig) {
testConfig.addBuild(
name: 'linux/host_debug',
dimension: TestDroneDimension.linux,
enableLto: false,
);
});
expect(
plan.useLto,
isFalse,
reason: 'Not specified and the build config included --no-lto',
);
expect(plan.toGnArgs(), contains('--no-lto'));
});
test('concurrency defaults to null if not specified', () {
final plan = configureAndParse([]);
expect(plan.concurrency, isNull);
});
test('concurrency parses the number provided', () {
final plan = configureAndParse(['--concurrency=1024']);
expect(plan.concurrency, 1024);
});
test('strategy defaults to auto', () {
final plan = configureAndParse([]);
expect(plan.strategy, BuildStrategy.auto);
expect(
plan.toRbeConfig(),
same(const RbeConfig()),
reason: 'Auto should use the default RbeConfig instance.',
);
});
test('strategy can be set to --local', () {
final plan = configureAndParse(['--build-strategy=local']);
expect(plan.strategy, BuildStrategy.local);
expect(
plan.toRbeConfig(),
same(const RbeConfig(
execStrategy: RbeExecStrategy.local,
remoteDisabled: true,
)),
reason: 'Local should use RbeExecStrategy.local with RBE disabled',
);
});
test('strategy can be set to --remote', () {
final plan = configureAndParse(
['--build-strategy=remote'],
environment: TestEnvironment.withTestEngine(
withRbe: true,
),
);
expect(plan.strategy, BuildStrategy.remote);
expect(
plan.toRbeConfig(),
same(const RbeConfig(execStrategy: RbeExecStrategy.remote)),
reason: 'Local should use RbeExecStrategy.remote',
);
});
test('can provide a single extra "--gn-args"', () {
final result = configureAndParse(['--gn-args', '--foo']);
expect(result.extraGnArgs, ['--foo']);
});
test('can provide multiple extra "--gn-arg"s', () {
final result = configureAndParse([
'--gn-args',
'--foo',
'--gn-args',
'--bar',
]);
expect(result.extraGnArgs, ['--foo', '--bar']);
});
test('build defaults to host_debug', () {
final plan = configureAndParse([]);
expect(plan.build.name, 'linux/host_debug');
});
test('build defaults to the provided default', () {
final testEnv = TestEnvironment.withTestEngine(
withRbe: true,
);
addTearDown(testEnv.cleanup);
final testConfig = TestBuilderConfig();
testConfig.addBuild(
name: 'linux/host_debug_unopt_arm64',
dimension: TestDroneDimension.linux,
);
final parser = ArgParser();
final builds = BuildPlan.configureArgParser(
parser,
testEnv.environment,
configs: {
'linux_test_config': testConfig.buildConfig(
path: 'ci/builders/linux_test_config.json',
),
},
help: false,
);
final plan = BuildPlan.fromArgResults(
parser.parse([]),
testEnv.environment,
builds: builds,
defaultBuild: () => 'host_debug_unopt_arm64',
);
expect(plan.build.name, 'linux/host_debug_unopt_arm64');
});
// These tests should strictly check for build failures as a result of
// invalid combination of flags or flags with invalid values; i.e. a valid
// BuildPlan is not returned.
group('prevents invalid flags', () {
test('can provide only long-form "--gn-arg"s', () {
expect(
() => configureAndParse(['--gn-args', '-F']),
throwsA(BuildPlan.argumentsMustBeFlagsError),
);
});
test('strategy of --remote with RBE disabled fails', () {
expect(
() => configureAndParse(
['--build-strategy=remote'],
environment: TestEnvironment.withTestEngine(
// This is the default, but make it explicit for the test.
// ignore: avoid_redundant_argument_values
withRbe: false,
),
),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains('Cannot use remote builds without RBE enabled'),
)),
);
});
test('rbe forced to true if not detected is an error', () {
expect(
() => configureAndParse(
['--rbe'],
environment: TestEnvironment.withTestEngine(
// This is the default, but make it explicit for the test.
// ignore: avoid_redundant_argument_values
withRbe: false,
),
),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains('RBE requested but configuration not found'),
)),
);
});
test('concurrency fails on a non-integer', () {
expect(
() => configureAndParse(['--concurrency=ABCD']),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains('Invalid value for --concurrency: ABCD'),
)),
);
});
test('concurrency fails on a negative integer', () {
expect(
() => configureAndParse(['--concurrency=-1024']),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains('Invalid value for --concurrency: -1024'),
)),
);
});
test('build fails if host_debug not specified and no config set', () {
expect(
() => configureAndParse([], builds: (testConfig) {
testConfig.addBuild(
name: 'linux/host_debug_unopt_arm64',
dimension: TestDroneDimension.linux,
);
}),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains('Unknown build configuration: host_debug'),
)),
);
});
group('build fails if an extra "--gn-args" contains a reserved flag', () {
for (final reserved in BuildPlan.reservedGnArgs) {
test(reserved, () {
expect(
() => configureAndParse(['--gn-args', '--$reserved']),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains(BuildPlan.reservedGnArgsError.toString()),
)),
);
});
}
});
test('builds fails if a non-flag (space) is provided to "--gn-args"', () {
expect(
() => configureAndParse(['--gn-args', '--foo name']),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains(BuildPlan.argumentsMustBeFlagsError.toString()),
)),
);
});
test('builds fails if a non-flag (with =) is provided to "--gn-args"', () {
expect(
() => configureAndParse(['--gn-args', '--foo=name']),
throwsA(isA<FatalError>().having(
(e) => e.toString(),
'toString()',
contains('Arguments provided to --gn-args must be flags'),
)),
);
});
test('build fails if a config not available is requested', () {
expect(
() => configureAndParse(['--config=host_debug_unopt_arm64']),
throwsA(
isA<ArgParserException>().having(
(e) => e.toString(),
'toString()',
contains(
'"host_debug_unopt_arm64" is not an allowed value for option',
),
),
),
);
});
});
// These tests should strictly check the usage (--help) output.
group('shows instructions based on verbosity', () {
/// Creates a configured [ArgParser] based on then environemnt and builds.
///
/// By default a non-verbose, non-RBE, Linux x64 configured [BuildPlan]
/// is created and used to configure the available options and instructions
/// for the returned [ArgParser].
///
/// - To use a different [TestEnvironment], provide [environment].
/// - To use a different set of builds, provide [builds].
ArgParser createArgParser({
TestEnvironment? environment,
void Function(TestBuilderConfig)? builds,
}) {
final (_, parser, _) = _createTestFixture(
environment: environment,
builds: builds,
);
return parser;
}
test('show builds in help message as long as not a [ci/...] build', () {
final parser = createArgParser(
builds: (testConfig) {
testConfig.addBuild(
name: 'ci/host_debug',
dimension: TestDroneDimension.linux,
);
testConfig.addBuild(
name: 'linux/host_debug',
dimension: TestDroneDimension.linux,
);
},
);
expect(parser.usage, contains('host_debug'));
expect(parser.usage, isNot(contains('ci/host_debug')));
});
test('shows [ci/...] builds if verbose is true', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
verbose: true,
),
builds: (testConfig) {
testConfig.addBuild(
name: 'ci/host_debug',
dimension: TestDroneDimension.linux,
);
testConfig.addBuild(
name: 'linux/host_debug',
dimension: TestDroneDimension.linux,
);
},
);
expect(parser.usage, contains('host_debug'));
expect(parser.usage, contains('ci/host_debug'));
});
test('hides LTO instructions normally', () {
final parser = createArgParser();
expect(
parser.usage,
isNot(contains('Whether LTO should be enabled for a build')),
);
});
test('shows LTO instructions if verbose', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
verbose: true,
),
);
expect(
parser.usage,
contains('Whether LTO should be enabled for a build'),
);
});
test('shows RBE instructions if not configured', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
// This is the default, but make it explicit for the test.
// ignore: avoid_redundant_argument_values
withRbe: false,
),
);
expect(
parser.usage,
stringContainsInOrder([
'Enable pre-configured remote build execution',
'https://flutter.dev/to/engine-rbe',
]),
);
});
test('shows RBE instructions if verbose', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
verbose: true,
),
);
expect(
parser.usage,
stringContainsInOrder([
'Enable pre-configured remote build execution',
'https://flutter.dev/to/engine-rbe',
]),
);
});
test('hides RBE instructions if enabled', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
withRbe: true,
),
);
expect(
parser.usage,
contains('Enable pre-configured remote build execution'),
);
expect(
parser.usage,
isNot(contains('https://flutter.dev/to/engine-rbe')),
);
});
test('hides --build-strategy if RBE not enabled', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
// This is the default, but make it explicit for the test.
// ignore: avoid_redundant_argument_values
withRbe: false,
),
);
expect(
parser.usage,
isNot(contains('How to prefer remote or local builds')),
);
});
test('shows --build-strategy if RBE enabled', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
withRbe: true,
),
);
expect(
parser.usage,
contains('How to prefer remote or local builds'),
);
});
test('shows --build-strategy if verbose', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
verbose: true,
),
);
expect(
parser.usage,
contains('How to prefer remote or local builds'),
);
});
test('hides --gn-args if not verbose', () {
final parser = createArgParser();
expect(
parser.usage,
isNot(contains('Additional arguments to provide to "gn"')),
);
});
test('shows --gn-args if verbose', () {
final parser = createArgParser(
environment: TestEnvironment.withTestEngine(
verbose: true,
),
);
expect(
parser.usage,
contains('Additional arguments to provide to "gn"'),
);
});
});
}
(List<Build>, ArgParser, Environment) _createTestFixture({
TestEnvironment? environment,
void Function(TestBuilderConfig)? builds,
}) {
final testEnv = environment ?? TestEnvironment.withTestEngine();
addTearDown(testEnv.cleanup);
final testConfig = TestBuilderConfig();
if (builds != null) {
builds(testConfig);
} else {
testConfig.addBuild(
name: 'linux/host_debug',
dimension: TestDroneDimension.linux,
);
}
final parser = ArgParser();
final builders = BuildPlan.configureArgParser(
parser,
testEnv.environment,
configs: {
'linux_test_config': testConfig.buildConfig(
path: 'ci/builders/linux_test_config.json',
),
},
help: true,
);
return (builders, parser, testEnv.environment);
}