1// Copyright 2016 The Chromium 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 5import 'dart:async'; 6 7import 'package:args/command_runner.dart'; 8import 'package:flutter_tools/src/base/common.dart'; 9import 'package:flutter_tools/src/base/file_system.dart'; 10import 'package:flutter_tools/src/base/platform.dart'; 11import 'package:flutter_tools/src/base/process.dart'; 12import 'package:flutter_tools/src/commands/create.dart'; 13import 'package:flutter_tools/src/runner/flutter_command.dart'; 14import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; 15import 'package:test_api/test_api.dart' as test_package show TypeMatcher; 16import 'package:test_api/test_api.dart' hide TypeMatcher, isInstanceOf; 17 18export 'package:test_core/test_core.dart' hide TypeMatcher, isInstanceOf; // Defines a 'package:test' shim. 19 20/// A matcher that compares the type of the actual value to the type argument T. 21// TODO(ianh): Remove this once https://github.com/dart-lang/matcher/issues/98 is fixed 22Matcher isInstanceOf<T>() => test_package.TypeMatcher<T>(); 23 24void tryToDelete(Directory directory) { 25 // This should not be necessary, but it turns out that 26 // on Windows it's common for deletions to fail due to 27 // bogus (we think) "access denied" errors. 28 try { 29 directory.deleteSync(recursive: true); 30 } on FileSystemException catch (error) { 31 print('Failed to delete ${directory.path}: $error'); 32 } 33} 34 35/// Gets the path to the root of the Flutter repository. 36/// 37/// This will first look for a `FLUTTER_ROOT` environment variable. If the 38/// environment variable is set, it will be returned. Otherwise, this will 39/// deduce the path from `platform.script`. 40String getFlutterRoot() { 41 if (platform.environment.containsKey('FLUTTER_ROOT')) 42 return platform.environment['FLUTTER_ROOT']; 43 44 Error invalidScript() => StateError('Invalid script: ${platform.script}'); 45 46 Uri scriptUri; 47 switch (platform.script.scheme) { 48 case 'file': 49 scriptUri = platform.script; 50 break; 51 case 'data': 52 final RegExp flutterTools = RegExp(r'(file://[^"]*[/\\]flutter_tools[/\\][^"]+\.dart)', multiLine: true); 53 final Match match = flutterTools.firstMatch(Uri.decodeFull(platform.script.path)); 54 if (match == null) 55 throw invalidScript(); 56 scriptUri = Uri.parse(match.group(1)); 57 break; 58 default: 59 throw invalidScript(); 60 } 61 62 final List<String> parts = fs.path.split(fs.path.fromUri(scriptUri)); 63 final int toolsIndex = parts.indexOf('flutter_tools'); 64 if (toolsIndex == -1) 65 throw invalidScript(); 66 final String toolsPath = fs.path.joinAll(parts.sublist(0, toolsIndex + 1)); 67 return fs.path.normalize(fs.path.join(toolsPath, '..', '..')); 68} 69 70CommandRunner<void> createTestCommandRunner([ FlutterCommand command ]) { 71 final FlutterCommandRunner runner = FlutterCommandRunner(); 72 if (command != null) 73 runner.addCommand(command); 74 return runner; 75} 76 77/// Updates [path] to have a modification time [seconds] from now. 78void updateFileModificationTime( 79 String path, 80 DateTime baseTime, 81 int seconds, 82) { 83 final DateTime modificationTime = baseTime.add(Duration(seconds: seconds)); 84 fs.file(path).setLastModifiedSync(modificationTime); 85} 86 87/// Matcher for functions that throw [ToolExit]. 88Matcher throwsToolExit({ int exitCode, Pattern message }) { 89 Matcher matcher = isToolExit; 90 if (exitCode != null) 91 matcher = allOf(matcher, (ToolExit e) => e.exitCode == exitCode); 92 if (message != null) 93 matcher = allOf(matcher, (ToolExit e) => e.message.contains(message)); 94 return throwsA(matcher); 95} 96 97/// Matcher for [ToolExit]s. 98final Matcher isToolExit = isInstanceOf<ToolExit>(); 99 100/// Matcher for functions that throw [ProcessExit]. 101Matcher throwsProcessExit([ dynamic exitCode ]) { 102 return exitCode == null 103 ? throwsA(isProcessExit) 104 : throwsA(allOf(isProcessExit, (ProcessExit e) => e.exitCode == exitCode)); 105} 106 107/// Matcher for [ProcessExit]s. 108final Matcher isProcessExit = isInstanceOf<ProcessExit>(); 109 110/// Creates a flutter project in the [temp] directory using the 111/// [arguments] list if specified, or `--no-pub` if not. 112/// Returns the path to the flutter project. 113Future<String> createProject(Directory temp, { List<String> arguments }) async { 114 arguments ??= <String>['--no-pub']; 115 final String projectPath = fs.path.join(temp.path, 'flutter_project'); 116 final CreateCommand command = CreateCommand(); 117 final CommandRunner<void> runner = createTestCommandRunner(command); 118 await runner.run(<String>['create', ...arguments, projectPath]); 119 return projectPath; 120} 121 122/// Test case timeout for tests involving remote calls to `pub get` or similar. 123const Timeout allowForRemotePubInvocation = Timeout.factor(10.0); 124 125/// Test case timeout for tests involving creating a Flutter project with 126/// `--no-pub`. Use [allowForRemotePubInvocation] when creation involves `pub`. 127const Timeout allowForCreateFlutterProject = Timeout.factor(3.0); 128 129Future<void> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async { 130 try { 131 await future; 132 fail('ToolExit expected, but nothing thrown'); 133 } on ToolExit catch(e) { 134 expect(e.message, messageMatcher); 135 } catch(e, trace) { 136 fail('ToolExit expected, got $e\n$trace'); 137 } 138} 139