| // 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. |
| |
| // This test builds an integration test from the list of samples in the |
| // examples/api/lib directory, and then runs it. The tests are just smoke tests, |
| // designed to start up each example and run it for a couple of frames to make |
| // sure it doesn't throw an exception or fail to compile. |
| |
| import 'dart:async'; |
| import 'dart:convert'; |
| import 'dart:io' show Process, ProcessException, exitCode, stderr, stdout; |
| |
| import 'package:file/file.dart'; |
| import 'package:file/local.dart'; |
| import 'package:path/path.dart' as path; |
| import 'package:platform/platform.dart'; |
| import 'package:process/process.dart'; |
| |
| FileSystem filesystem = const LocalFileSystem(); |
| ProcessManager processManager = const LocalProcessManager(); |
| Platform platform = const LocalPlatform(); |
| |
| FutureOr<dynamic> main() async { |
| if (!platform.isLinux && !platform.isWindows && !platform.isMacOS) { |
| stderr.writeln('Example smoke tests are only designed to run on desktop platforms'); |
| exitCode = 4; |
| return; |
| } |
| final Directory flutterDir = filesystem.directory( |
| path.absolute( |
| path.dirname( |
| path.dirname( |
| path.dirname(platform.script.toFilePath()), |
| ), |
| ), |
| ), |
| ); |
| final Directory apiDir = flutterDir.childDirectory('examples').childDirectory('api'); |
| final File integrationTest = await generateTest(apiDir); |
| try { |
| await runSmokeTests(flutterDir: flutterDir, integrationTest: integrationTest, apiDir: apiDir); |
| } finally { |
| await cleanUp(integrationTest); |
| } |
| } |
| |
| Future<void> cleanUp(File integrationTest) async { |
| try { |
| await integrationTest.delete(); |
| // Delete the integration_test directory if it is empty. |
| await integrationTest.parent.delete(); |
| } on FileSystemException { |
| // Ignore, there might be other files in there preventing it from |
| // being removed, or it might not exist. |
| } |
| } |
| |
| // Executes the generated smoke test. |
| Future<void> runSmokeTests({ |
| required Directory flutterDir, |
| required File integrationTest, |
| required Directory apiDir, |
| }) async { |
| final File flutterExe = |
| flutterDir.childDirectory('bin').childFile(platform.isWindows ? 'flutter.bat' : 'flutter'); |
| final List<String> cmd = <String>[ |
| // If we're in a container with no X display, then use the virtual framebuffer. |
| if (platform.isLinux && |
| (platform.environment['DISPLAY'] == null || |
| platform.environment['DISPLAY']!.isEmpty)) '/usr/bin/xvfb-run', |
| flutterExe.absolute.path, |
| 'test', |
| '--reporter=expanded', |
| '--device-id=${platform.operatingSystem}', |
| integrationTest.absolute.path, |
| ]; |
| await runCommand(cmd, workingDirectory: apiDir); |
| } |
| |
| // A class to hold information related to an example, used to generate names |
| // from for the tests. |
| class ExampleInfo { |
| ExampleInfo(this.file, Directory examplesLibDir) |
| : importPath = _getImportPath(file, examplesLibDir), |
| importName = '' { |
| importName = importPath.replaceAll(RegExp(r'\.dart$'), '').replaceAll(RegExp(r'\W'), '_'); |
| } |
| |
| final File file; |
| final String importPath; |
| String importName; |
| |
| static String _getImportPath(File example, Directory examplesLibDir) { |
| final String relativePath = |
| path.relative(example.absolute.path, from: examplesLibDir.absolute.path); |
| // So that Windows paths are proper URIs in the import statements. |
| return path.toUri(relativePath).toFilePath(windows: false); |
| } |
| } |
| |
| // Generates the combined smoke test. |
| Future<File> generateTest(Directory apiDir) async { |
| final Directory examplesLibDir = apiDir.childDirectory('lib'); |
| |
| // Get files from git, to avoid any non-repo files that might be in someone's |
| // workspace. |
| final List<String> gitFiles = (await runCommand( |
| <String>['git', 'ls-files', '**/*.dart'], |
| workingDirectory: examplesLibDir, |
| quiet: true, |
| )).replaceAll(r'\', '/') |
| .trim() |
| .split('\n'); |
| final Iterable<File> examples = gitFiles.map<File>((String examplePath) { |
| return filesystem.file(path.join(examplesLibDir.absolute.path, examplePath)); |
| }); |
| |
| // Collect the examples, and import them all as separate symbols. |
| final List<String> imports = <String>[]; |
| imports.add('''import 'package:flutter/widgets.dart';'''); |
| imports.add('''import 'package:flutter/scheduler.dart';'''); |
| imports.add('''import 'package:flutter_test/flutter_test.dart';'''); |
| imports.add('''import 'package:integration_test/integration_test.dart';'''); |
| final List<ExampleInfo> infoList = <ExampleInfo>[]; |
| for (final File example in examples) { |
| final ExampleInfo info = ExampleInfo(example, examplesLibDir); |
| infoList.add(info); |
| imports.add('''import 'package:flutter_api_samples/${info.importPath}' as ${info.importName};'''); |
| } |
| imports.sort(); |
| infoList.sort((ExampleInfo a, ExampleInfo b) => a.importPath.compareTo(b.importPath)); |
| |
| final StringBuffer buffer = StringBuffer(); |
| buffer.writeln('// Temporary generated file. Do not commit.'); |
| buffer.writeln("import 'dart:io';"); |
| buffer.writeAll(imports, '\n'); |
| buffer.writeln(r''' |
| |
| |
| import '../../../dev/manual_tests/test/mock_image_http.dart'; |
| |
| void main() { |
| IntegrationTestWidgetsFlutterBinding? binding; |
| try { |
| binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized() as IntegrationTestWidgetsFlutterBinding; |
| } catch (e) { |
| stderr.writeln('Unable to initialize binding${binding == null ? '' : ' $binding'}: $e'); |
| exitCode = 128; |
| return; |
| } |
| |
| '''); |
| for (final ExampleInfo info in infoList) { |
| buffer.writeln(''' |
| testWidgets( |
| 'Smoke test ${info.importPath}', |
| (WidgetTester tester) async { |
| final ErrorWidgetBuilder originalBuilder = ErrorWidget.builder; |
| try { |
| HttpOverrides.runZoned(() { |
| ${info.importName}.main(); |
| }, createHttpClient: (SecurityContext? context) => FakeHttpClient(context)); |
| await tester.pump(); |
| await tester.pump(); |
| expect(find.byType(WidgetsApp), findsOneWidget); |
| } finally { |
| ErrorWidget.builder = originalBuilder; |
| timeDilation = 1.0; |
| } |
| }, |
| ); |
| '''); |
| } |
| buffer.writeln('}'); |
| |
| final File integrationTest = |
| apiDir.childDirectory('integration_test').childFile('smoke_integration_test.dart'); |
| integrationTest.createSync(recursive: true); |
| integrationTest.writeAsStringSync(buffer.toString()); |
| return integrationTest; |
| } |
| |
| // Run a command, and optionally stream the output as it runs, returning the |
| // stdout. |
| Future<String> runCommand( |
| List<String> cmd, { |
| required Directory workingDirectory, |
| bool quiet = false, |
| List<String>? output, |
| Map<String, String>? environment, |
| }) async { |
| final List<int> stdoutOutput = <int>[]; |
| final List<int> combinedOutput = <int>[]; |
| final Completer<void> stdoutComplete = Completer<void>(); |
| final Completer<void> stderrComplete = Completer<void>(); |
| |
| late Process process; |
| Future<int> allComplete() async { |
| await stderrComplete.future; |
| await stdoutComplete.future; |
| return process.exitCode; |
| } |
| |
| try { |
| process = await processManager.start( |
| cmd, |
| workingDirectory: workingDirectory.absolute.path, |
| environment: environment, |
| ); |
| process.stdout.listen( |
| (List<int> event) { |
| stdoutOutput.addAll(event); |
| combinedOutput.addAll(event); |
| if (!quiet) { |
| stdout.add(event); |
| } |
| }, |
| onDone: () async => stdoutComplete.complete(), |
| ); |
| process.stderr.listen( |
| (List<int> event) { |
| combinedOutput.addAll(event); |
| if (!quiet) { |
| stderr.add(event); |
| } |
| }, |
| onDone: () async => stderrComplete.complete(), |
| ); |
| } on ProcessException catch (e) { |
| stderr.writeln('Running "${cmd.join(' ')}" in ${workingDirectory.path} ' |
| 'failed with:\n$e'); |
| exitCode = 2; |
| return utf8.decode(stdoutOutput); |
| } on ArgumentError catch (e) { |
| stderr.writeln('Running "${cmd.join(' ')}" in ${workingDirectory.path} ' |
| 'failed with:\n$e'); |
| exitCode = 3; |
| return utf8.decode(stdoutOutput); |
| } |
| |
| final int processExitCode = await allComplete(); |
| if (processExitCode != 0) { |
| stderr.writeln('Running "${cmd.join(' ')}" in ${workingDirectory.path} exited with code $processExitCode'); |
| exitCode = processExitCode; |
| } |
| |
| if (output != null) { |
| output.addAll(utf8.decode(combinedOutput).split('\n')); |
| } |
| |
| return utf8.decode(stdoutOutput); |
| } |