// 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/adb.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)); }); } /// 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'); /// 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 (buildOnly && applicationBinaryPath != null) { throw Exception('Application binary path is only used for tests'); } if (!testOnly) { build(); } if (buildOnly) { return TaskResult.buildOnly(); } return test(); } }