Propagate environment variables when flutter drive is invoked. (#161452)

Closes https://github.com/flutter/flutter/issues/161449.

~3 LOC, with 203 lines of tests (including an e2e integration test that
it actually works).

Feedback welcome!

(The reason I'm working on this is the ability to pass environment
variables makes it much easier and less hacky to make
`android_engine_test` configurable, i.e. have different expected outputs
for OpenGLES/Vulkan, compare screenshots locally for deflaking, etc).
This commit is contained in:
Matan Lurey 2025-01-13 10:48:58 -08:00 committed by GitHub
parent 6e8d80743d
commit 449079d4f1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 101 additions and 23 deletions

View File

@ -65,6 +65,7 @@ class DriveCommand extends RunCommandBase {
}) : _flutterDriverFactory = flutterDriverFactory,
_fileSystem = fileSystem,
_logger = logger,
_platform = platform,
_fsUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform),
super(verboseHelp: verboseHelp) {
requiresPubspecYaml();
@ -205,6 +206,7 @@ class DriveCommand extends RunCommandBase {
FlutterDriverFactory? _flutterDriverFactory;
final FileSystem _fileSystem;
final Logger _logger;
final Platform _platform;
final FileSystemUtils _fsUtils;
Timer? timeoutTimer;
Map<ProcessSignal, Object>? screenshotTokens;
@ -292,6 +294,7 @@ class DriveCommand extends RunCommandBase {
_flutterDriverFactory ??= FlutterDriverFactory(
applicationPackageFactory: ApplicationPackageFactory.instance!,
logger: _logger,
platform: _platform,
processUtils: globals.processUtils,
dartSdkPath: globals.artifacts!.getArtifactPath(Artifact.engineDartBinary),
devtoolsLauncher: DevtoolsLauncher.instance!,
@ -336,7 +339,6 @@ class DriveCommand extends RunCommandBase {
final Future<int> testResultFuture = driverService.startTest(
testFile,
stringsArg('test-arguments'),
<String, String>{},
packageConfig,
chromeBinary: stringArg('chrome-binary'),
headless: boolArg('headless'),

View File

@ -13,6 +13,7 @@ import '../application_package.dart';
import '../base/common.dart';
import '../base/dds.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../build_info.dart';
import '../device.dart';
@ -24,17 +25,20 @@ import 'web_driver_service.dart';
class FlutterDriverFactory {
FlutterDriverFactory({
required ApplicationPackageFactory applicationPackageFactory,
required Platform platform,
required Logger logger,
required ProcessUtils processUtils,
required String dartSdkPath,
required DevtoolsLauncher devtoolsLauncher,
}) : _applicationPackageFactory = applicationPackageFactory,
_platform = platform,
_logger = logger,
_processUtils = processUtils,
_dartSdkPath = dartSdkPath,
_devtoolsLauncher = devtoolsLauncher;
final ApplicationPackageFactory _applicationPackageFactory;
final Platform _platform;
final Logger _logger;
final ProcessUtils _processUtils;
final String _dartSdkPath;
@ -45,12 +49,14 @@ class FlutterDriverFactory {
if (web) {
return WebDriverService(
logger: _logger,
platform: _platform,
processUtils: _processUtils,
dartSdkPath: _dartSdkPath,
);
}
return FlutterDriverService(
logger: _logger,
platform: _platform,
processUtils: _processUtils,
dartSdkPath: _dartSdkPath,
applicationPackageFactory: _applicationPackageFactory,
@ -84,7 +90,6 @@ abstract class DriverService {
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment,
PackageConfig packageConfig, {
bool? headless,
String? chromeBinary,
@ -110,12 +115,14 @@ class FlutterDriverService extends DriverService {
FlutterDriverService({
required ApplicationPackageFactory applicationPackageFactory,
required Logger logger,
required Platform platform,
required ProcessUtils processUtils,
required String dartSdkPath,
required DevtoolsLauncher devtoolsLauncher,
@visibleForTesting VMServiceConnector vmServiceConnector = connectToVmService,
}) : _applicationPackageFactory = applicationPackageFactory,
_logger = logger,
_platform = platform,
_processUtils = processUtils,
_dartSdkPath = dartSdkPath,
_vmServiceConnector = vmServiceConnector,
@ -125,6 +132,7 @@ class FlutterDriverService extends DriverService {
final ApplicationPackageFactory _applicationPackageFactory;
final Logger _logger;
final Platform _platform;
final ProcessUtils _processUtils;
final String _dartSdkPath;
final VMServiceConnector _vmServiceConnector;
@ -227,7 +235,6 @@ class FlutterDriverService extends DriverService {
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment,
PackageConfig packageConfig, {
bool? headless,
String? chromeBinary,
@ -251,7 +258,7 @@ class FlutterDriverService extends DriverService {
try {
final int result = await _processUtils.stream(
<String>[_dartSdkPath, ...arguments, testFile],
environment: <String, String>{'VM_SERVICE_URL': _vmServiceUri, ...environment},
environment: <String, String>{..._platform.environment, 'VM_SERVICE_URL': _vmServiceUri},
);
return result;
} finally {

View File

@ -13,6 +13,7 @@ import 'package:webdriver/async_io.dart' as async_io;
import '../base/common.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/platform.dart';
import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
@ -29,13 +30,16 @@ class WebDriverService extends DriverService {
WebDriverService({
required ProcessUtils processUtils,
required String dartSdkPath,
required Platform platform,
required Logger logger,
}) : _processUtils = processUtils,
_dartSdkPath = dartSdkPath,
_platform = platform,
_logger = logger;
final ProcessUtils _processUtils;
final String _dartSdkPath;
final Platform _platform;
final Logger _logger;
late ResidentRunner _residentRunner;
@ -143,7 +147,6 @@ class WebDriverService extends DriverService {
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment,
PackageConfig packageConfig, {
bool? headless,
String? chromeBinary,
@ -195,9 +198,9 @@ class WebDriverService extends DriverService {
final int result = await _processUtils.stream(
<String>[_dartSdkPath, ...arguments, testFile],
environment: <String, String>{
..._platform.environment,
'VM_SERVICE_URL': _webUri.toString(),
..._additionalDriverEnvironment(webDriver, browserName, androidEmulator),
...environment,
},
);
await webDriver.quit();

View File

@ -702,7 +702,6 @@ class NeverEndingDriverService extends Fake implements DriverService {
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment,
PackageConfig packageConfig, {
bool? headless,
String? chromeBinary,
@ -736,7 +735,6 @@ class FailingFakeDriverService extends Fake implements DriverService {
Future<int> startTest(
String testFile,
List<String> arguments,
Map<String, String> environment,
PackageConfig packageConfig, {
bool? headless,
String? chromeBinary,

View File

@ -10,6 +10,7 @@ import 'package:flutter_tools/src/application_package.dart';
import 'package:flutter_tools/src/base/dds.dart';
import 'package:flutter_tools/src/base/io.dart' as io;
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
@ -139,6 +140,7 @@ void main() {
final DriverService driverService = setUpDriverService(
processManager: processManager,
vmService: fakeVmServiceHost.vmService,
platform: FakePlatform(environment: <String, String>{'FOO': 'BAR'}),
);
final Device device = FakeDevice(
LaunchResult.succeeded(vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/')),
@ -149,12 +151,9 @@ void main() {
device,
DebuggingOptions.enabled(BuildInfo.profile, ipv6: true),
);
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
PackageConfig(<Package>[Package('test', Uri.base)]),
);
final int testResult = await driverService.startTest('foo.test', <String>[
'--enable-experiment=non-nullable',
], PackageConfig(<Package>[Package('test', Uri.base)]));
expect(testResult, 23);
});
@ -180,6 +179,7 @@ void main() {
processManager: processManager,
vmService: fakeVmServiceHost.vmService,
devtoolsLauncher: launcher,
platform: FakePlatform(environment: <String, String>{'FOO': 'BAR'}),
);
final Device device = FakeDevice(
LaunchResult.succeeded(vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/')),
@ -193,7 +193,6 @@ void main() {
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
PackageConfig(<Package>[Package('test', Uri.base)]),
profileMemory: 'devtools_memory.json',
);
@ -222,6 +221,7 @@ void main() {
final DriverService driverService = setUpDriverService(
processManager: processManager,
vmService: fakeVmServiceHost.vmService,
platform: FakePlatform(environment: <String, String>{'FOO': 'BAR'}),
);
final Device device = FakeDevice(
LaunchResult.succeeded(vmServiceUri: Uri.parse('http://127.0.0.1:63426/1UasC_ihpXY=/')),
@ -232,12 +232,9 @@ void main() {
device,
DebuggingOptions.enabled(BuildInfo.profile, ipv6: true),
);
final int testResult = await driverService.startTest(
'foo.test',
<String>['--enable-experiment=non-nullable'],
<String, String>{'FOO': 'BAR'},
PackageConfig.empty,
);
final int testResult = await driverService.startTest('foo.test', <String>[
'--enable-experiment=non-nullable',
], PackageConfig.empty);
expect(testResult, 23);
},
@ -276,7 +273,6 @@ void main() {
final int testResult = await driverService.startTest(
'foo.test',
<String>[],
<String, String>{},
PackageConfig(<Package>[Package('test', Uri.base)]),
);
@ -483,6 +479,7 @@ void main() {
FlutterDriverService setUpDriverService({
Logger? logger,
Platform? platform,
ProcessManager? processManager,
FlutterVmService? vmService,
DevtoolsLauncher? devtoolsLauncher,
@ -491,6 +488,7 @@ FlutterDriverService setUpDriverService({
return FlutterDriverService(
applicationPackageFactory: FakeApplicationPackageFactory(FakeApplicationPackage()),
logger: logger,
platform: platform ?? FakePlatform(),
processUtils: ProcessUtils(
logger: logger,
processManager: processManager ?? FakeProcessManager.any(),

View File

@ -7,6 +7,7 @@ import 'dart:async';
import 'package:file/file.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/net.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/build_info.dart';
@ -411,6 +412,7 @@ WebDriverService setUpDriverService() {
logger: logger,
processUtils: ProcessUtils(logger: logger, processManager: FakeProcessManager.any()),
dartSdkPath: 'dart',
platform: FakePlatform(),
);
}

View File

@ -0,0 +1,67 @@
// 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 'package:file/file.dart';
import 'package:flutter_tools/src/base/io.dart';
import '../src/common.dart';
import 'test_data/project.dart';
import 'test_utils.dart';
void main() {
late Directory tempDir;
setUp(() async {
tempDir = fileSystem.systemTempDirectory.createTempSync('driver_environment_test.');
});
tearDown(() async {
tryToDelete(tempDir);
});
testWithoutContext('environment variables are passed to the drive script', () async {
final Project project = _PrintEnvironmentVariablesInTestDriverProject();
await project.setUpIn(tempDir);
final ProcessResult result = await processManager.run(
<String>[flutterBin, 'drive', '-d', 'flutter-tester'],
workingDirectory: tempDir.path,
environment: <String, String>{'FOO': 'BAR'},
);
printOnFailure('stdout: ${result.stdout}');
printOnFailure('stderr: ${result.stderr}');
expect(result.exitCode, 0);
expect(result.stdout.toString(), contains('FOO=BAR'));
});
}
final class _PrintEnvironmentVariablesInTestDriverProject extends Project {
@override
final String pubspec = '''
name: test
environment:
sdk: ^3.7.0-0
dependencies:
flutter:
sdk: flutter
''';
@override
final String main = r'void main() {}';
@override
Future<void> setUpIn(Directory dir) async {
await super.setUpIn(dir);
writeFile(fileSystem.path.join(dir.path, 'test_driver', 'main_test.dart'), r'''
import 'dart:io' as io;
void main() {
print('FOO=${io.Platform.environment['FOO']}');
}
''');
}
}

View File

@ -4,6 +4,7 @@
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/drive/web_driver_service.dart';
import 'package:package_config/package_config_types.dart';
@ -20,13 +21,13 @@ void main() {
logger: logger,
processUtils: ProcessUtils(logger: logger, processManager: FakeProcessManager.empty()),
dartSdkPath: 'dart',
platform: FakePlatform(),
);
const String link = 'https://flutter.dev/to/integration-test-on-web';
try {
await service.startTest(
'foo.test',
<String>[],
<String, String>{},
PackageConfig(<Package>[Package('test', Uri.base)]),
driverPort: 1,
headless: true,