blob: 7709f49725dacef4db73c486d270824e2bdd064f [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;
import 'dart:ffi';
import 'package:engine_build_configs/engine_build_configs.dart';
import 'package:engine_tool/src/build_utils.dart';
import 'package:engine_tool/src/commands/command_runner.dart';
import 'package:engine_tool/src/logger.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import 'fixtures.dart' as fixtures;
import 'src/test_build_configs.dart';
import 'utils.dart';
void main() {
test('can find host runnable build', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'macos/host_debug',
dimension: TestDroneDimension.mac,
);
builder.addBuild(
name: 'mac/host_profile',
dimension: TestDroneDimension.mac,
);
builder.addBuild(
name: 'linux/host_debug',
dimension: TestDroneDimension.linux,
);
final configs = {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
};
final result = runnableBuilds(testEnv.environment, configs, true);
expect(
result.map((r) => r.name),
unorderedEquals(['macos/host_debug', 'mac/host_profile']),
);
});
test('build command invokes gn', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'macos/host_debug',
dimension: TestDroneDimension.mac,
);
final configs = {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
};
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'host_debug',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
expect(testEnv.processHistory.length, greaterThanOrEqualTo(1));
expect(testEnv.processHistory[0].command[0], contains('gn'));
});
test('build command invokes ninja', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'macos/host_debug',
dimension: TestDroneDimension.mac,
);
final configs = {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
};
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'host_debug',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
expect(testEnv.processHistory.length, greaterThanOrEqualTo(2));
expect(testEnv.processHistory[1].command[0], contains('ninja'));
});
test('build command invokes generator', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'macos/host_debug',
dimension: TestDroneDimension.mac,
generatorTask: ('gen/script.py', ['--test-param']),
);
final configs = {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
};
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'host_debug',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
expect(
testEnv.processHistory.map((p) => p.command),
containsOnce(containsAllInOrder(['python3', 'gen/script.py'])),
);
});
test('build command does not invoke tests', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'macos/host_debug',
dimension: TestDroneDimension.mac,
testTask: ('test/script.py', ['--test-param']),
);
final configs = {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
};
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'host_debug',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
expect(
testEnv.processHistory.map((p) => p.command),
isNot(contains(containsAllInOrder(['python3', 'gen/script.py']))),
);
});
test('build command runs rbe on an rbe build', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
withRbe: true,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/android_debug_rbe_arm64',
dimension: TestDroneDimension.mac,
enableRbe: true,
);
final configs = {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
};
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'ci/android_debug_rbe_arm64',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
final [gnCall, reclientCall, ..._] = testEnv.processHistory;
expect(
gnCall.command,
containsAllInOrder([
endsWith('tools/gn'),
contains('--rbe'),
]),
);
expect(
reclientCall.command,
containsAllInOrder([
endsWith('reclient/bootstrap'),
]),
);
});
test('build command plumbs -j to ninja', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
withRbe: true,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/android_debug_arm64',
dimension: TestDroneDimension.mac,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/android_debug_arm64',
'-j',
'500',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
print(testEnv.processHistory);
final [_, ninja, ..._] = testEnv.processHistory;
expect(
ninja.command,
containsAllInOrder([
endsWith('ninja/ninja'),
'-j',
'500',
]),
);
});
test('build command fails when rbe is enabled but not supported', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
// Intentionally omit withRbe: true.
// That means the //flutter/build/rbe directory will not be created.
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/android_debug_rbe_arm64',
dimension: TestDroneDimension.mac,
enableRbe: true,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/android_debug_rbe_arm64',
'--rbe',
]);
expect(result, equals(1));
expect(
testEnv.testLogs.map((LogRecord r) => r.message).join(),
contains('RBE was requested but no RBE config was found'),
);
});
test('build command does not run rbe when disabled', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
withRbe: true,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/android_debug_rbe_arm64',
dimension: TestDroneDimension.mac,
// Intentionally show that RBE is disabled.
// ignore: avoid_redundant_argument_values
enableRbe: false,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/android_debug_rbe_arm64',
'--no-rbe',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
final [gn, ninja, ..._] = testEnv.processHistory;
expect(gn.command, isNot(contains('--rbe')));
expect(
ninja.command,
containsAllInOrder(
[
endsWith('ninja/ninja'),
],
),
);
});
test('build command does not run rbe when rbe configs do not exist',
() async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/android_debug_rbe_arm64',
dimension: TestDroneDimension.mac,
enableRbe: true,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/android_debug_rbe_arm64',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
final [gn, ninja, ..._] = testEnv.processHistory;
expect(gn.command, isNot(contains('--rbe')));
expect(
ninja.command,
containsAllInOrder(
[
endsWith('ninja/ninja'),
],
),
);
});
test('mangleConfigName removes the OS and adds ci/ as needed', () {
final testEnv = TestEnvironment.withTestEngine();
addTearDown(testEnv.cleanup);
final env = testEnv.environment;
expect(mangleConfigName(env, 'linux/build'), equals('build'));
expect(mangleConfigName(env, 'ci/build'), equals('ci/build'));
});
test('mangleConfigName throws when the input config name is malformed', () {
final testEnv = TestEnvironment.withTestEngine();
addTearDown(testEnv.cleanup);
final env = testEnv.environment;
expect(
() => mangleConfigName(env, 'build'),
throwsArgumentError,
);
});
test('demangleConfigName adds the OS and removes ci/ as needed', () {
final testEnv = TestEnvironment.withTestEngine();
addTearDown(testEnv.cleanup);
final env = testEnv.environment;
expect(demangleConfigName(env, 'build'), equals('linux/build'));
expect(demangleConfigName(env, 'ci/build'), equals('ci/build'));
});
test('local config name on the command line is correctly translated',
() async {
final namespaceTestConfigs = BuilderConfig.fromJson(
path: 'ci/builders/namespace_test_config.json',
map: convert.jsonDecode(fixtures.configsToTestNamespacing)
as Map<String, Object?>,
);
final configs = <String, BuilderConfig>{
'namespace_test_config': namespaceTestConfigs,
};
final testEnv = TestEnvironment.withTestEngine();
addTearDown(testEnv.cleanup);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'host_debug',
]);
expect(result, equals(0));
expect(testEnv.processHistory[1].command[0],
contains(path.join('ninja', 'ninja')));
expect(testEnv.processHistory[1].command[2], contains('local_host_debug'));
});
test('ci config name on the command line is correctly translated', () async {
final namespaceTestConfigs = BuilderConfig.fromJson(
path: 'ci/builders/namespace_test_config.json',
map: convert.jsonDecode(fixtures.configsToTestNamespacing)
as Map<String, Object?>,
);
final configs = <String, BuilderConfig>{
'namespace_test_config': namespaceTestConfigs,
};
final testEnv = TestEnvironment.withTestEngine();
addTearDown(testEnv.cleanup);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: configs,
);
final result = await runner.run([
'build',
'--config',
'ci/host_debug',
]);
expect(result, equals(0));
expect(testEnv.processHistory[1].command[0],
contains(path.join('ninja', 'ninja')));
expect(testEnv.processHistory[1].command[2], contains('ci/host_debug'));
});
test('build command invokes ninja with the specified target', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
cannedProcesses: [
CannedProcess(
(command) => command.contains('desc'),
stdout: convert.jsonEncode({
'//flutter/fml:fml_arc_unittests': {
'outputs': [
'//out/host_debug/fml_arc_unittests',
],
'testonly': true,
'type': 'executable',
},
}),
),
],
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/host_debug',
targetDir: 'host_debug',
dimension: TestDroneDimension.mac,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/host_debug',
'//flutter/fml:fml_arc_unittests',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
final ninjaCmd = testEnv.processHistory.firstWhere(
(p) => p.command.first.endsWith('ninja'),
);
expect(
ninjaCmd.command,
containsAllInOrder(
[
endsWith('ninja'),
'-C',
endsWith('host_debug'),
],
),
);
expect(
ninjaCmd.command,
contains(
contains('flutter/fml:fml_arc_unittests'),
),
);
});
test('build command invokes ninja with all matched targets', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
cannedProcesses: [
CannedProcess(
(command) => command.contains('desc'),
stdout: convert.jsonEncode({
'//flutter/display_list:display_list_unittests': {
'outputs': [
'//out/host_debug/display_list_unittests',
],
'testonly': true,
'type': 'executable',
},
'//flutter/flow:flow_unittests': {
'outputs': [
'//out/host_debug/flow_unittests',
],
'testonly': true,
'type': 'executable',
},
'//flutter/fml:fml_arc_unittests': {
'outputs': [
'//out/host_debug/fml_arc_unittests',
],
'testonly': true,
'type': 'executable',
},
}),
),
],
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/host_debug',
targetDir: 'host_debug',
dimension: TestDroneDimension.mac,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/host_debug',
'//flutter/...',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
final ninjaCmd = testEnv.processHistory.firstWhere(
(p) => p.command.first.endsWith('ninja'),
);
expect(
ninjaCmd.command,
containsAllInOrder(
[
endsWith('ninja'),
'-C',
endsWith('host_debug'),
],
),
);
expect(
ninjaCmd.command,
containsAll([
'flutter/display_list:display_list_unittests',
'flutter/flow:flow_unittests',
'flutter/fml:fml_arc_unittests',
]),
);
});
test('build command gracefully handles no matched targets', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
cannedProcesses: [
CannedProcess(
(command) => command.contains('desc'),
stdout: '''
The input testing/scenario_app:sceario_app matches no targets, configs or files.
''',
exitCode: 1,
),
],
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/host_debug',
targetDir: 'host_debug',
dimension: TestDroneDimension.mac,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'mac_test_config': builder.buildConfig(
path: 'ci/builders/mac_test_config.json',
),
},
);
final result = await runner.run([
'build',
'--config',
'ci/host_debug',
// Intentionally omit the prefix '//flutter/' to trigger the warning.
'//testing/scenario_app',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
expect(
testEnv.testLogs.map((LogRecord r) => r.message).join(),
contains('No targets matched the pattern `testing/scenario_app'),
);
});
test('et help build line length is not too big', () async {
final testEnv = TestEnvironment.withTestEngine(
verbose: true,
);
addTearDown(testEnv.cleanup);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {},
help: true,
);
final result = await runner.run([
'help',
'build',
]);
expect(result, equals(0));
// Avoid a degenerate case where nothing is logged.
expect(testEnv.testLogs, isNotEmpty, reason: 'No logs were emitted');
expect(
testEnv.testLogs.map((LogRecord r) => r.message.split('\n')),
everyElement(hasLength(lessThanOrEqualTo(100))),
);
});
test('verbose "et help build" contains CI builds', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
verbose: true,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/linux_android_debug',
dimension: TestDroneDimension.mac,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'linux_test_config': builder.buildConfig(
path: 'ci/builders/linux_test_config.json',
)
},
help: true,
);
final result = await runner.run([
'--verbose',
'help',
'build',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
// Avoid a degenerate case where nothing is logged.
expect(testEnv.testLogs, isNotEmpty, reason: 'No logs were emitted');
print(testEnv.testLogs);
expect(
testEnv.testLogs.map((LogRecord r) => r.message),
contains(contains('[ci/')),
);
});
test('non-verbose "et help build" does not contain ci builds', () async {
final testEnv = TestEnvironment.withTestEngine(
abi: Abi.macosArm64,
);
addTearDown(testEnv.cleanup);
final builder = TestBuilderConfig();
builder.addBuild(
name: 'ci/linux_android_debug',
dimension: TestDroneDimension.mac,
);
final runner = ToolCommandRunner(
environment: testEnv.environment,
configs: {
'linux_test_config': builder.buildConfig(
path: 'ci/builders/linux_test_config.json',
)
},
help: true,
);
final result = await runner.run([
'help',
'build',
]);
printOnFailure(testEnv.testLogs.map((r) => r.message).join('\n'));
expect(result, equals(0));
// Avoid a degenerate case where nothing is logged.
expect(testEnv.testLogs, isNotEmpty, reason: 'No logs were emitted');
expect(
testEnv.testLogs.map((LogRecord r) => r.message),
isNot(contains(contains('[ci/'))),
reason: 'The log should not contain CI-prefixed builds',
);
});
}