| // 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() | |
| ..addOption(kApplicationBinaryPathOption) | |
| ..addFlag(kBuildOnlyFlag) | |
| ..addFlag(kTestOnlyFlag); | |
| /// 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'); | |
| } | |
| section('BUILDING APPLICATION'); | |
| await flutter('build', options: getBuildArgs(deviceOperatingSystem)); | |
| copyArtifacts(); | |
| }); | |
| } | |
| /// 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(); | |
| } | |
| } |