Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 070290b

Browse files
authored
Refactor args parsing/environment constructor for scenario_app (#50980)
This moves the ever-growing amount of options and defaults into it's own class(es). The test runner itself has no tests (yet), but this shim will make writing tests easier. I tried to make no other real changes to how the runner functions in this PR.
1 parent 86f7742 commit 070290b

File tree

5 files changed

+423
-188
lines changed

5 files changed

+423
-188
lines changed

testing/scenario_app/bin/README.md

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,40 @@ dart bin/android_integration_tests.dart --smoke-test dev.flutter.scenarios.Engin
2424

2525
## Additional arguments
2626

27-
- `--verbose`: Print additional information about the test run.
28-
29-
- `--adb`: The path to the `adb` tool. Defaults to
30-
`third_party/android_tools/sdk/platform-tools/adb`.
31-
32-
- `--out-dir`: The directory containing the build artifacts. Defaults to the
33-
last updated build directory in `out/` that starts with `android_`.
34-
35-
- `--logs-dir`: The directory to store logs and screenshots. Defaults to
36-
`FLUTTER_LOGS_DIR` if set, or `out/.../scenario_app/logs` otherwise.
37-
38-
- `--use-skia-gold`: Use Skia Gold to compare screenshots. Defaults to true
39-
when running on CI, and false otherwise (i.e. when running locally). If
40-
set to true, `isSkiaGoldClientAvailable` must be true.
41-
42-
- `--enable-impeller`: Enable Impeller for the Android app. Defaults to
43-
false, which means that the app will use Skia as the graphics backend.
27+
```txt
28+
-v, --verbose Enable verbose logging
29+
-h, --help Print usage information
30+
--[no-]enable-impeller Whether to enable Impeller as the graphics backend. If true, the
31+
test runner will use --impeller-backend if set, otherwise the
32+
default backend will be used. To explicitly run with the Skia
33+
backend, set this to false (--no-enable-impeller).
34+
--impeller-backend The graphics backend to use when --enable-impeller is true. Unlike
35+
the similar option when launching an app, there is no fallback;
36+
that is, either Vulkan or OpenGLES must be specified.
37+
[vulkan (default), opengles]
38+
--logs-dir Path to a directory where logs and screenshots are stored.
39+
--out-dir=<path/to/out/android_variant> Path to a out/{variant} directory where the APKs are built.
40+
Defaults to the latest updated out/ directory that starts with
41+
"android_" if the current working directory is within the engine
42+
repository.
43+
--smoke-test=<package.ClassName> Fully qualified class name of a single test to run. For example try
44+
"dev.flutter.scenarios.EngineLaunchE2ETest" or
45+
"dev.flutter.scenariosui.ExternalTextureTests".
46+
--output-contents-golden=<path/to/golden.txt> Path to a file that contains the expected filenames of golden
47+
files. If the current working directory is within the engine
48+
repository, defaults to
49+
./testing/scenario_app/android/expected_golden_output.txt.
50+
```
4451

45-
- `--impeller-backend`: The Impeller backend to use for the Android app.
46-
Defaults to 'vulkan'. Only used when `--enable-impeller` is set to true.
52+
## Advanced usage
53+
54+
```txt
55+
--[no-]use-skia-gold Whether to use Skia Gold to compare screenshots. Defaults to true
56+
on CI and false otherwise.
57+
--adb=<path/to/adb> Path to the Android Debug Bridge (adb) executable. If the current
58+
working directory is within the engine repository, defaults to
59+
./third_party/android_tools/sdk/platform-tools/adb.
60+
--ndk-stack=<path/to/ndk-stack> Path to the NDK stack tool. Defaults to the checked-in version in
61+
third_party/android_tools if the current working directory is
62+
within the engine repository on a supported platform.
63+
```

testing/scenario_app/bin/run_android_tests.dart

Lines changed: 60 additions & 169 deletions
Original file line numberDiff line numberDiff line change
@@ -7,188 +7,80 @@ import 'dart:convert';
77
import 'dart:io';
88
import 'dart:typed_data';
99

10-
import 'package:args/args.dart';
1110
import 'package:dir_contents_diff/dir_contents_diff.dart' show dirContentsDiff;
1211
import 'package:engine_repo_tools/engine_repo_tools.dart';
1312
import 'package:path/path.dart';
1413
import 'package:process/process.dart';
1514
import 'package:skia_gold_client/skia_gold_client.dart';
1615

1716
import 'utils/adb_logcat_filtering.dart';
17+
import 'utils/environment.dart';
1818
import 'utils/logs.dart';
19+
import 'utils/options.dart';
1920
import 'utils/process_manager_extension.dart';
2021
import 'utils/screenshot_transformer.dart';
2122

22-
void _withTemporaryCwd(String path, void Function() callback) {
23-
final String originalCwd = Directory.current.path;
24-
Directory.current = Directory(path).parent.path;
23+
// If you update the arguments, update the documentation in the README.md file.
24+
void main(List<String> args) async {
25+
// Get some basic environment information to guide the rest of the program.
26+
final Environment environment = Environment(
27+
isCi: Platform.environment['LUCI_CONTEXT'] != null,
28+
showVerbose: Options.showVerbose(args),
29+
logsDir: Platform.environment['FLUTTER_LOGS_DIR'],
30+
);
2531

26-
try {
27-
callback();
28-
} finally {
29-
Directory.current = originalCwd;
32+
// Determine if the CWD is within an engine checkout.
33+
final Engine? localEngineDir = Engine.tryFindWithin();
34+
35+
// Show usage if requested.
36+
if (Options.showUsage(args)) {
37+
stdout.writeln(Options.usage(
38+
environment: environment,
39+
localEngineDir: localEngineDir,
40+
));
41+
return;
3042
}
31-
}
3243

33-
// If you update the arguments, update the documentation in the README.md file.
34-
void main(List<String> args) async {
35-
final Engine? engine = Engine.tryFindWithin();
36-
final ArgParser parser = ArgParser()
37-
..addFlag(
38-
'help',
39-
help: 'Prints usage information',
40-
negatable: false,
41-
)
42-
..addFlag(
43-
'verbose',
44-
help: 'Prints verbose output',
45-
negatable: false,
46-
)
47-
..addOption(
48-
'adb',
49-
help: 'Path to the adb tool',
50-
defaultsTo: engine != null
51-
? join(
52-
engine.srcDir.path,
53-
'third_party',
54-
'android_tools',
55-
'sdk',
56-
'platform-tools',
57-
'adb',
58-
)
59-
: null,
60-
)
61-
..addOption(
62-
'ndk-stack',
63-
help: 'Path to the ndk-stack tool',
64-
defaultsTo: engine != null
65-
? join(
66-
engine.srcDir.path,
67-
'third_party',
68-
'android_tools',
69-
'ndk',
70-
'prebuilt',
71-
() {
72-
if (Platform.isLinux) {
73-
return 'linux-x86_64';
74-
} else if (Platform.isMacOS) {
75-
return 'darwin-x86_64';
76-
} else if (Platform.isWindows) {
77-
return 'windows-x86_64';
78-
} else {
79-
throw UnsupportedError('Unsupported platform: ${Platform.operatingSystem}');
80-
}
81-
}(),
82-
'bin',
83-
'ndk-stack',
84-
)
85-
: null,
86-
)
87-
..addOption(
88-
'out-dir',
89-
help: 'Out directory',
90-
defaultsTo: engine
91-
?.outputs()
92-
.where((Output o) => basename(o.path.path).startsWith('android_'))
93-
.firstOrNull
94-
?.path
95-
.path,
96-
)
97-
..addOption(
98-
'smoke-test',
99-
help: 'Runs a single test to verify the setup',
100-
)
101-
..addFlag(
102-
'use-skia-gold',
103-
help: 'Use Skia Gold to compare screenshots.',
104-
defaultsTo: isLuciEnv,
105-
)
106-
..addFlag(
107-
'enable-impeller',
108-
help: 'Enable Impeller for the Android app.',
109-
)
110-
..addOption(
111-
'output-contents-golden',
112-
help: 'Path to a file that contains the expected filenames of golden files.',
113-
defaultsTo: engine != null
114-
? join(
115-
engine.srcDir.path,
116-
'flutter',
117-
'testing',
118-
'scenario_app',
119-
'android',
120-
'expected_golden_output.txt',
121-
)
122-
: null,
123-
)
124-
..addOption(
125-
'impeller-backend',
126-
help: 'The Impeller backend to use for the Android app.',
127-
allowed: <String>['vulkan', 'opengles'],
128-
defaultsTo: 'vulkan',
129-
)
130-
..addOption(
131-
'logs-dir',
132-
help: 'The directory to store the logs and screenshots. Defaults to '
133-
'the value of the FLUTTER_LOGS_DIR environment variable, if set, '
134-
'otherwise it defaults to a path within out-dir.',
135-
defaultsTo: Platform.environment['FLUTTER_LOGS_DIR'],
44+
// Parse the command line arguments.
45+
final Options options;
46+
try {
47+
options = Options.parse(
48+
args,
49+
environment: environment,
50+
localEngine: localEngineDir,
13651
);
52+
} on FormatException catch (error) {
53+
stderr.writeln(error);
54+
stderr.writeln(Options.usage(
55+
environment: environment,
56+
localEngineDir: localEngineDir,
57+
));
58+
exitCode = 1;
59+
return;
60+
}
13761

13862
runZonedGuarded(
13963
() async {
140-
final ArgResults results = parser.parse(args);
141-
if (results['help'] as bool) {
142-
stdout.writeln(parser.usage);
143-
return;
144-
}
145-
146-
if (results['out-dir'] == null) {
147-
panic(<String>['--out-dir is required']);
148-
}
149-
if (results['adb'] == null) {
150-
panic(<String>['--adb is required']);
151-
}
152-
153-
final bool verbose = results['verbose'] as bool;
154-
final Directory outDir = Directory(results['out-dir'] as String);
155-
final File adb = File(results['adb'] as String);
156-
final bool useSkiaGold = results['use-skia-gold'] as bool;
157-
final String? smokeTest = results['smoke-test'] as String?;
158-
final bool enableImpeller = results['enable-impeller'] as bool;
159-
final String? contentsGolden = results['output-contents-golden'] as String?;
160-
final _ImpellerBackend? impellerBackend = _ImpellerBackend.tryParse(results['impeller-backend'] as String?);
161-
if (enableImpeller && impellerBackend == null) {
162-
panic(<String>[
163-
'invalid graphics-backend',
164-
results['impeller-backend'] as String? ?? '<null>'
165-
]);
166-
}
167-
final Directory logsDir = Directory(results['logs-dir'] as String? ?? join(outDir.path, 'scenario_app', 'logs'));
168-
final String? ndkStack = results['ndk-stack'] as String?;
169-
if (ndkStack == null) {
170-
panic(<String>['--ndk-stack is required']);
171-
}
17264
await _run(
173-
verbose: verbose,
174-
outDir: outDir,
175-
adb: adb,
176-
smokeTestFullPath: smokeTest,
177-
useSkiaGold: useSkiaGold,
178-
enableImpeller: enableImpeller,
179-
impellerBackend: impellerBackend,
180-
logsDir: logsDir,
181-
contentsGolden: contentsGolden,
182-
ndkStack: ndkStack,
65+
verbose: options.verbose,
66+
outDir: Directory(options.outDir),
67+
adb: File(options.adb),
68+
smokeTestFullPath: options.smokeTest,
69+
useSkiaGold: options.useSkiaGold,
70+
enableImpeller: options.enableImpeller,
71+
impellerBackend: _ImpellerBackend.tryParse(options.impellerBackend),
72+
logsDir: Directory(options.logsDir),
73+
contentsGolden: options.outputContentsGolden,
74+
ndkStack: options.ndkStack,
18375
);
18476
exit(0);
18577
},
18678
(Object error, StackTrace stackTrace) {
18779
if (error is! Panic) {
188-
stderr.writeln(error);
80+
stderr.writeln('Unhandled error: $error');
18981
stderr.writeln(stackTrace);
19082
}
191-
exit(1);
83+
exitCode = 1;
19284
},
19385
);
19486
}
@@ -222,18 +114,6 @@ Future<void> _run({
222114
required String ndkStack,
223115
}) async {
224116
const ProcessManager pm = LocalProcessManager();
225-
226-
if (!outDir.existsSync()) {
227-
panic(<String>[
228-
'out-dir does not exist: $outDir',
229-
'make sure to build the selected engine variant'
230-
]);
231-
}
232-
233-
if (!adb.existsSync()) {
234-
panic(<String>['cannot find adb: $adb', 'make sure to run gclient sync']);
235-
}
236-
237117
final String scenarioAppPath = join(outDir.path, 'scenario_app');
238118
final String logcatPath = join(logsDir.path, 'logcat.txt');
239119

@@ -521,7 +401,7 @@ Future<void> _run({
521401
// TODO(matanlurey): Resolve this in a better way. On CI this file always exists.
522402
File(join(screenshotPath, 'noop.txt')).writeAsStringSync('');
523403
// TODO(gaaclarke): We should move this into dir_contents_diff.
524-
_withTemporaryCwd(contentsGolden, () {
404+
_withTemporaryCwd(dirname(contentsGolden), () {
525405
final int exitCode = dirContentsDiff(basename(contentsGolden), screenshotPath);
526406
if (exitCode != 0) {
527407
panic(<String>['Output contents incorrect.']);
@@ -531,3 +411,14 @@ Future<void> _run({
531411
}
532412
}
533413
}
414+
415+
void _withTemporaryCwd(String path, void Function() callback) {
416+
final String originalCwd = Directory.current.path;
417+
Directory.current = Directory(path).path;
418+
419+
try {
420+
callback();
421+
} finally {
422+
Directory.current = originalCwd;
423+
}
424+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:meta/meta.dart';
6+
7+
/// An overridable collection of values provided by the environment.
8+
@immutable
9+
final class Environment {
10+
/// Creates a new environment from the given values.
11+
const Environment({
12+
required this.isCi,
13+
required this.showVerbose,
14+
required this.logsDir,
15+
});
16+
17+
/// Whether the current program is running on a CI environment.
18+
///
19+
/// Useful for determining if certain features should be enabled or disabled
20+
/// based on the environment, or to add safety checks (for example, using
21+
/// confusing or ambiguous flags).
22+
final bool isCi;
23+
24+
/// Whether the user has requested verbose logging and program output.
25+
final bool showVerbose;
26+
27+
/// What directory to store logs and screenshots in.
28+
final String? logsDir;
29+
30+
@override
31+
bool operator ==(Object o) {
32+
return o is Environment &&
33+
o.isCi == isCi &&
34+
o.showVerbose == showVerbose &&
35+
o.logsDir == logsDir;
36+
}
37+
38+
@override
39+
int get hashCode => Object.hash(isCi, showVerbose, logsDir);
40+
41+
@override
42+
String toString() {
43+
return 'Environment(isCi: $isCi, showVerbose: $showVerbose, logsDir: $logsDir)';
44+
}
45+
}

0 commit comments

Comments
 (0)