blob: 2405763fd147239761a666f1d75d8ec1b03f3012 [file] [log] [blame]
// Copyright 2014 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:io';
import 'package:args/args.dart';
import '../framework/devices.dart';
import '../framework/task_result.dart';
import '../framework/utils.dart';
/// [Task] for defining build-test separation.
/// Using this [Task] allows DeviceLab capacity to only be spent on the [test].
abstract class BuildTestTask {
BuildTestTask(this.args, {this.workingDirectory, this.runFlutterClean = true,}) {
final ArgResults argResults = argParser.parse(args);
applicationBinaryPath = argResults[kApplicationBinaryPathOption] as String?;
buildOnly = argResults[kBuildOnlyFlag] as bool;
testOnly = argResults[kTestOnlyFlag] as bool;
static const String kApplicationBinaryPathOption = 'application-binary-path';
static const String kBuildOnlyFlag = 'build';
static const String kTestOnlyFlag = 'test';
final ArgParser argParser = ArgParser()
/// Args passed from the test runner via "--task-arg".
final List<String> args;
/// If true, skip [test].
bool buildOnly = false;
/// If true, skip [build].
bool testOnly = false;
/// Whether to run `flutter clean` before building the application under test.
final bool runFlutterClean;
/// Path to a built application to use in [test].
/// If not given, will default to child's expected location.
String? applicationBinaryPath;
/// Where the test artifacts are stored, such as performance results.
final Directory? workingDirectory;
/// Run Flutter build to create [applicationBinaryPath].
Future<void> build() async {
await inDirectory<void>(workingDirectory, () async {
if (runFlutterClean) {
section('FLUTTER CLEAN');
await flutter('clean');
await flutter('build', options: getBuildArgs(deviceOperatingSystem));
/// Run Flutter drive test from [getTestArgs] against the application under test on the device.
/// This assumes that [applicationBinaryPath] exists.
Future<TaskResult> test() async {
final Device device = await devices.workingDevice;
await device.unlock();
await inDirectory<void>(workingDirectory, () async {
section('DRIVE START');
await flutter('drive', options: getTestArgs(deviceOperatingSystem, device.deviceId));
return parseTaskResult();
/// Args passed to flutter build to build the application under test.
List<String> getBuildArgs(DeviceOperatingSystem deviceOperatingSystem) => throw UnimplementedError('getBuildArgs is not implemented');
/// Args passed to flutter drive to test the built application.
List<String> getTestArgs(DeviceOperatingSystem deviceOperatingSystem, String deviceId) => throw UnimplementedError('getTestArgs is not implemented');
/// Copy artifacts to [applicationBinaryPath] if specified.
/// This is needed when running from CI, so that LUCI recipes know where to locate and upload artifacts to GCS.
void copyArtifacts() => throw UnimplementedError('copyArtifacts is not implemented');
/// Logic to construct [TaskResult] from this test's results.
Future<TaskResult> parseTaskResult() => throw UnimplementedError('parseTaskResult is not implemented');
/// Path to the built application under test.
/// Tasks can override to support default values. Otherwise, it will default
/// to needing to be passed as an argument in the test runner.
String? getApplicationBinaryPath() => applicationBinaryPath;
/// Run this task.
/// Throws [Exception] when unnecessary arguments are passed.
Future<TaskResult> call() async {
if (buildOnly && testOnly) {
throw Exception('Both build and test should not be passed. Pass only one.');
if (!testOnly) {
await build();
if (buildOnly) {
return TaskResult.buildOnly();
return test();