{"action":"created","comment":{"url":"https://api.github.com/repos/flutter/flutter/pulls/comments/90913890","pull_request_review_id":11424881,"id":90913890,"diff_hunk":"@@ -479,3 +491,294 @@ class _RecordingProcess implements Process {\n   @override\n   bool kill([ProcessSignal signal = ProcessSignal.SIGTERM]) => delegate.kill(signal);\n }\n+\n+/// a [ProcessManager] implementation that mocks out all process invocations\n+/// by replaying a previously-recorded series of invocations, throwing an\n+/// exception if the requested invocations substantively differ in any way\n+/// from those in the recording.\n+///\n+/// Recordings are expected to be of the form produced by\n+/// [RecordingProcessManager]. Namely, this includes:\n+///\n+/// - a [_kManifestName](manifest file) encoded as UTF-8 JSON that lists all\n+///   invocations in order, along with the following metadata for each\n+///   invocation:\n+///   - `pid` (required): The process id integer.\n+///   - `basename` (required): A string specifying the base filename from which\n+///     the incovation's `stdout` and `stderr` files can be located.\n+///   - `executable` (required): A string specifying the path to the executable\n+///     command that kicked off the process.\n+///   - `arguments` (required): A list of strings that were passed as arguments\n+///     to the executable.\n+///   - `workingDirectory` (required): The current working directory from which\n+///     the process was spawned.\n+///   - `environment` (required): A map from string environment variable keys\n+///     to their corresponding string values.\n+///   - `mode` (optional): A string specifying the [ProcessStartMode].\n+///   - `stdoutEncoding` (optional): The name of the encoding scheme that was\n+///     used in the `stdout` file. If unspecified, then the file was written\n+///     as binary data.\n+///   - `stderrEncoding` (optional): The name of the encoding scheme that was\n+///     used in the `stderr` file. If unspecified, then the file was written\n+///     as binary data.\n+///   - `exitCode` (required): The exit code of the process, or null if the\n+///     process was not responding.\n+///   - `daemon` (optional): A boolean indicating that the process is to stay\n+///     resident during the entire lifetime of the master Flutter tools process.\n+/// - a `stdout` file for each process invocation. The location of this file\n+///   can be derived from the `basename` manifest property like so:\n+///   `'$basename.stdout'`.\n+/// - a `stderr` file for each process invocation. The location of this file\n+///   can be derived from the `basename` manifest property like so:\n+///   `'$basename.stderr'`.\n+class ReplayProcessManager implements ProcessManager {\n+  final List<Map<String, dynamic>> _manifest;\n+  final Directory _dir;\n+\n+  ReplayProcessManager._(this._manifest, this._dir);\n+\n+  /// Creates a new `ReplayProcessManager` capable of replaying a recording at\n+  /// the specified location.\n+  ///\n+  /// If [location] represents a file, it will be treated like a recording\n+  /// ZIP file. If it points to a directory, it will be treated like an\n+  /// unzipped recording. If [location] points to a non-existent file or\n+  /// directory, an [ArgumentError] will be thrown.\n+  static Future<ReplayProcessManager> create(String location) async {\n+    Directory dir;\n+    switch (await FileSystemEntity.type(location)) {\n+      case FileSystemEntityType.FILE:\n+        dir = await Directory.systemTemp.createTemp('flutter_tools_');\n+        os.unzip(new File(location), dir);\n+        addShutdownHook(() async {\n+          await dir.delete(recursive: true);\n+        });\n+        break;\n+      case FileSystemEntityType.DIRECTORY:\n+        dir = new Directory(location);\n+        break;\n+      case FileSystemEntityType.NOT_FOUND:\n+        throw new ArgumentError.value(location, 'location');\n+    }\n+\n+    File manifestFile = new File(path.join(dir.path, _kManifestName));\n+    if (!(await manifestFile.exists())) {\n+      // We use the existence of the manifest as a proxy for this being a\n+      // valid replay directory. Namely, we don't validate the structure of the\n+      // JSON within the manifest, and we don't validate the existence of\n+      // all stdout and stderr files referenced in the manifest.\n+      throw new ArgumentError('$_kManifestName not found.');\n+    }\n+\n+    String content = await manifestFile.readAsString();\n+    try {\n+      List<Map<String, dynamic>> manifest = new JsonDecoder().convert(content);\n+      return new ReplayProcessManager._(manifest, dir);\n+    } on FormatException catch (_) {\n+      throw new ArgumentError('$_kManifestName is not a valid JSON file.');\n+    }\n+  }\n+\n+  @override\n+  Future<Process> start(\n+      String executable,\n+      List<String> arguments,\n+      {String workingDirectory,\n+       Map<String, String> environment,\n+       ProcessStartMode mode: ProcessStartMode.NORMAL}) async {\n+    Map<String, dynamic> entry = _popEntry(executable, arguments, mode: mode);\n+    _ReplayProcessResult result = await _ReplayProcessResult.create(_dir, entry);\n+    return result.asProcess(entry['daemon'] ?? false);\n+  }\n+\n+  @override\n+  Future<ProcessResult> run(\n+      String executable,\n+      List<String> arguments,\n+      {String workingDirectory,\n+       Map<String, String> environment,\n+       Encoding stdoutEncoding: SYSTEM_ENCODING,\n+       Encoding stderrEncoding: SYSTEM_ENCODING}) async {\n+    Map<String, dynamic> entry = _popEntry(executable, arguments,\n+        stdoutEncoding: stdoutEncoding, stderrEncoding: stderrEncoding);\n+    return await _ReplayProcessResult.create(_dir, entry);\n+  }\n+\n+  @override\n+  ProcessResult runSync(\n+      String executable,\n+      List<String> arguments,\n+      {String workingDirectory,\n+       Map<String, String> environment,\n+       Encoding stdoutEncoding: SYSTEM_ENCODING,\n+       Encoding stderrEncoding: SYSTEM_ENCODING}) {\n+    Map<String, dynamic> entry = _popEntry(executable, arguments,\n+        stdoutEncoding: stdoutEncoding, stderrEncoding: stderrEncoding);\n+    return _ReplayProcessResult.createSync(_dir, entry);\n+  }\n+\n+  /// Finds and returns the next entry in the process manifest that matches\n+  /// the specified process arguments. Once found, it marks the manifest entry\n+  /// as having been invoked and thus not eligible for invocation again.\n+  Map<String, dynamic> _popEntry(String executable, List<String> arguments, {\n+    ProcessStartMode mode,\n+    Encoding stdoutEncoding,\n+    Encoding stderrEncoding,\n+  }) {\n+    ListsEqual<String> equal = const ListEquality<String>().equals;\n+    Map<String, dynamic> entry = _manifest.firstWhere(\n+      (Map<String, dynamic> entry) {\n+        // Ignore workingDirectory & environment, as they could\n+        // yield false negatives.\n+        return entry['executable'] == executable\n+            && equal(entry['arguments'], arguments)\n+            && entry['mode'] == mode?.toString()\n+            && entry['stdoutEncoding'] == stdoutEncoding?.name\n+            && entry['stderrEncoding'] == stderrEncoding?.name\n+            && !(entry['invoked'] ?? false);\n+      },\n+      orElse: () => null,\n+    );\n+\n+    if (entry == null)\n+      throw new StateError('No matching invocation found for $executable');\n+\n+    entry['invoked'] = true;\n+    return entry;\n+  }\n+\n+  @override\n+  bool killPid(int pid, [ProcessSignal signal = ProcessSignal.SIGTERM]) {\n+    throw new UnimplementedError();","path":"packages/flutter_tools/lib/src/base/process_manager.dart","position":309,"original_position":309,"commit_id":"574d516e6fb96f10059504916a2a909d7f34cd20","original_commit_id":"574d516e6fb96f10059504916a2a909d7f34cd20","user":{"login":"cbracken","id":351029,"avatar_url":"https://avatars.githubusercontent.com/u/351029?v=3","gravatar_id":"","url":"https://api.github.com/users/cbracken","html_url":"https://github.com/cbracken","followers_url":"https://api.github.com/users/cbracken/followers","following_url":"https://api.github.com/users/cbracken/following{/other_user}","gists_url":"https://api.github.com/users/cbracken/gists{/gist_id}","starred_url":"https://api.github.com/users/cbracken/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cbracken/subscriptions","organizations_url":"https://api.github.com/users/cbracken/orgs","repos_url":"https://api.github.com/users/cbracken/repos","events_url":"https://api.github.com/users/cbracken/events{/privacy}","received_events_url":"https://api.github.com/users/cbracken/received_events","type":"User","site_admin":false},"body":"`UnimplementedError` or `UnsupportedError`? If `Unimplemented`, add a TODO.","created_at":"2016-12-05T17:27:47Z","updated_at":"2016-12-05T17:28:33Z","html_url":"https://github.com/flutter/flutter/pull/7146#discussion_r90913890","pull_request_url":"https://api.github.com/repos/flutter/flutter/pulls/7146","_links":{"self":{"href":"https://api.github.com/repos/flutter/flutter/pulls/comments/90913890"},"html":{"href":"https://github.com/flutter/flutter/pull/7146#discussion_r90913890"},"pull_request":{"href":"https://api.github.com/repos/flutter/flutter/pulls/7146"}}},"pull_request":{"url":"https://api.github.com/repos/flutter/flutter/pulls/7146","id":96415598,"html_url":"https://github.com/flutter/flutter/pull/7146","diff_url":"https://github.com/flutter/flutter/pull/7146.diff","patch_url":"https://github.com/flutter/flutter/pull/7146.patch","issue_url":"https://api.github.com/repos/flutter/flutter/issues/7146","number":7146,"state":"open","locked":false,"title":"Add --replay-from argument to Flutter tools","user":{"login":"tvolkert","id":15253456,"avatar_url":"https://avatars.githubusercontent.com/u/15253456?v=3","gravatar_id":"","url":"https://api.github.com/users/tvolkert","html_url":"https://github.com/tvolkert","followers_url":"https://api.github.com/users/tvolkert/followers","following_url":"https://api.github.com/users/tvolkert/following{/other_user}","gists_url":"https://api.github.com/users/tvolkert/gists{/gist_id}","starred_url":"https://api.github.com/users/tvolkert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvolkert/subscriptions","organizations_url":"https://api.github.com/users/tvolkert/orgs","repos_url":"https://api.github.com/users/tvolkert/repos","events_url":"https://api.github.com/users/tvolkert/events{/privacy}","received_events_url":"https://api.github.com/users/tvolkert/received_events","type":"User","site_admin":false},"body":"This argument will enable mocking of os-layer process invocations,\r\nwhere the mock behavior will come from replaying a previously-\r\nrecorded set of invocations. At the point of process invocation,\r\nthe key metadata for the invocation will be looked up in the\r\nrecording's manifest, and iff a matching record exists in the\r\nmenifest, the process will be mocked out with data derived from\r\nthe corresponding recorded process (e.g. stdout, stderr, ecit code).","created_at":"2016-12-04T00:01:18Z","updated_at":"2016-12-05T17:36:47Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e5a12387a7d10d1db420117a8b8ca78e61b11ba6","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/flutter/flutter/pulls/7146/commits","review_comments_url":"https://api.github.com/repos/flutter/flutter/pulls/7146/comments","review_comment_url":"https://api.github.com/repos/flutter/flutter/pulls/comments{/number}","comments_url":"https://api.github.com/repos/flutter/flutter/issues/7146/comments","statuses_url":"https://api.github.com/repos/flutter/flutter/statuses/574d516e6fb96f10059504916a2a909d7f34cd20","head":{"label":"tvolkert:replay","ref":"replay","sha":"574d516e6fb96f10059504916a2a909d7f34cd20","user":{"login":"tvolkert","id":15253456,"avatar_url":"https://avatars.githubusercontent.com/u/15253456?v=3","gravatar_id":"","url":"https://api.github.com/users/tvolkert","html_url":"https://github.com/tvolkert","followers_url":"https://api.github.com/users/tvolkert/followers","following_url":"https://api.github.com/users/tvolkert/following{/other_user}","gists_url":"https://api.github.com/users/tvolkert/gists{/gist_id}","starred_url":"https://api.github.com/users/tvolkert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvolkert/subscriptions","organizations_url":"https://api.github.com/users/tvolkert/orgs","repos_url":"https://api.github.com/users/tvolkert/repos","events_url":"https://api.github.com/users/tvolkert/events{/privacy}","received_events_url":"https://api.github.com/users/tvolkert/received_events","type":"User","site_admin":false},"repo":{"id":59706548,"name":"flutter","full_name":"tvolkert/flutter","owner":{"login":"tvolkert","id":15253456,"avatar_url":"https://avatars.githubusercontent.com/u/15253456?v=3","gravatar_id":"","url":"https://api.github.com/users/tvolkert","html_url":"https://github.com/tvolkert","followers_url":"https://api.github.com/users/tvolkert/followers","following_url":"https://api.github.com/users/tvolkert/following{/other_user}","gists_url":"https://api.github.com/users/tvolkert/gists{/gist_id}","starred_url":"https://api.github.com/users/tvolkert/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tvolkert/subscriptions","organizations_url":"https://api.github.com/users/tvolkert/orgs","repos_url":"https://api.github.com/users/tvolkert/repos","events_url":"https://api.github.com/users/tvolkert/events{/privacy}","received_events_url":"https://api.github.com/users/tvolkert/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tvolkert/flutter","description":"Framework for building high-performance, high-fidelity iOS and Android apps.","fork":true,"url":"https://api.github.com/repos/tvolkert/flutter","forks_url":"https://api.github.com/repos/tvolkert/flutter/forks","keys_url":"https://api.github.com/repos/tvolkert/flutter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tvolkert/flutter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tvolkert/flutter/teams","hooks_url":"https://api.github.com/repos/tvolkert/flutter/hooks","issue_events_url":"https://api.github.com/repos/tvolkert/flutter/issues/events{/number}","events_url":"https://api.github.com/repos/tvolkert/flutter/events","assignees_url":"https://api.github.com/repos/tvolkert/flutter/assignees{/user}","branches_url":"https://api.github.com/repos/tvolkert/flutter/branches{/branch}","tags_url":"https://api.github.com/repos/tvolkert/flutter/tags","blobs_url":"https://api.github.com/repos/tvolkert/flutter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tvolkert/flutter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tvolkert/flutter/git/refs{/sha}","trees_url":"https://api.github.com/repos/tvolkert/flutter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tvolkert/flutter/statuses/{sha}","languages_url":"https://api.github.com/repos/tvolkert/flutter/languages","stargazers_url":"https://api.github.com/repos/tvolkert/flutter/stargazers","contributors_url":"https://api.github.com/repos/tvolkert/flutter/contributors","subscribers_url":"https://api.github.com/repos/tvolkert/flutter/subscribers","subscription_url":"https://api.github.com/repos/tvolkert/flutter/subscription","commits_url":"https://api.github.com/repos/tvolkert/flutter/commits{/sha}","git_commits_url":"https://api.github.com/repos/tvolkert/flutter/git/commits{/sha}","comments_url":"https://api.github.com/repos/tvolkert/flutter/comments{/number}","issue_comment_url":"https://api.github.com/repos/tvolkert/flutter/issues/comments{/number}","contents_url":"https://api.github.com/repos/tvolkert/flutter/contents/{+path}","compare_url":"https://api.github.com/repos/tvolkert/flutter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tvolkert/flutter/merges","archive_url":"https://api.github.com/repos/tvolkert/flutter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tvolkert/flutter/downloads","issues_url":"https://api.github.com/repos/tvolkert/flutter/issues{/number}","pulls_url":"https://api.github.com/repos/tvolkert/flutter/pulls{/number}","milestones_url":"https://api.github.com/repos/tvolkert/flutter/milestones{/number}","notifications_url":"https://api.github.com/repos/tvolkert/flutter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tvolkert/flutter/labels{/name}","releases_url":"https://api.github.com/repos/tvolkert/flutter/releases{/id}","deployments_url":"https://api.github.com/repos/tvolkert/flutter/deployments","created_at":"2016-05-26T00:15:42Z","updated_at":"2016-05-26T00:15:45Z","pushed_at":"2016-12-05T16:43:56Z","git_url":"git://github.com/tvolkert/flutter.git","ssh_url":"git@github.com:tvolkert/flutter.git","clone_url":"https://github.com/tvolkert/flutter.git","svn_url":"https://github.com/tvolkert/flutter","homepage":"https://flutter.io","size":21003,"stargazers_count":0,"watchers_count":0,"language":"Dart","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"flutter:master","ref":"master","sha":"d7c215199d479ab0659ecd9b47fa011ad1c60c8d","user":{"login":"flutter","id":14101776,"avatar_url":"https://avatars.githubusercontent.com/u/14101776?v=3","gravatar_id":"","url":"https://api.github.com/users/flutter","html_url":"https://github.com/flutter","followers_url":"https://api.github.com/users/flutter/followers","following_url":"https://api.github.com/users/flutter/following{/other_user}","gists_url":"https://api.github.com/users/flutter/gists{/gist_id}","starred_url":"https://api.github.com/users/flutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flutter/subscriptions","organizations_url":"https://api.github.com/users/flutter/orgs","repos_url":"https://api.github.com/users/flutter/repos","events_url":"https://api.github.com/users/flutter/events{/privacy}","received_events_url":"https://api.github.com/users/flutter/received_events","type":"Organization","site_admin":false},"repo":{"id":31792824,"name":"flutter","full_name":"flutter/flutter","owner":{"login":"flutter","id":14101776,"avatar_url":"https://avatars.githubusercontent.com/u/14101776?v=3","gravatar_id":"","url":"https://api.github.com/users/flutter","html_url":"https://github.com/flutter","followers_url":"https://api.github.com/users/flutter/followers","following_url":"https://api.github.com/users/flutter/following{/other_user}","gists_url":"https://api.github.com/users/flutter/gists{/gist_id}","starred_url":"https://api.github.com/users/flutter/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/flutter/subscriptions","organizations_url":"https://api.github.com/users/flutter/orgs","repos_url":"https://api.github.com/users/flutter/repos","events_url":"https://api.github.com/users/flutter/events{/privacy}","received_events_url":"https://api.github.com/users/flutter/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/flutter/flutter","description":"Framework for building high-performance, high-fidelity iOS and Android apps.","fork":false,"url":"https://api.github.com/repos/flutter/flutter","forks_url":"https://api.github.com/repos/flutter/flutter/forks","keys_url":"https://api.github.com/repos/flutter/flutter/keys{/key_id}","collaborators_url":"https://api.github.com/repos/flutter/flutter/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/flutter/flutter/teams","hooks_url":"https://api.github.com/repos/flutter/flutter/hooks","issue_events_url":"https://api.github.com/repos/flutter/flutter/issues/events{/number}","events_url":"https://api.github.com/repos/flutter/flutter/events","assignees_url":"https://api.github.com/repos/flutter/flutter/assignees{/user}","branches_url":"https://api.github.com/repos/flutter/flutter/branches{/branch}","tags_url":"https://api.github.com/repos/flutter/flutter/tags","blobs_url":"https://api.github.com/repos/flutter/flutter/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/flutter/flutter/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/flutter/flutter/git/refs{/sha}","trees_url":"https://api.github.com/repos/flutter/flutter/git/trees{/sha}","statuses_url":"https://api.github.com/repos/flutter/flutter/statuses/{sha}","languages_url":"https://api.github.com/repos/flutter/flutter/languages","stargazers_url":"https://api.github.com/repos/flutter/flutter/stargazers","contributors_url":"https://api.github.com/repos/flutter/flutter/contributors","subscribers_url":"https://api.github.com/repos/flutter/flutter/subscribers","subscription_url":"https://api.github.com/repos/flutter/flutter/subscription","commits_url":"https://api.github.com/repos/flutter/flutter/commits{/sha}","git_commits_url":"https://api.github.com/repos/flutter/flutter/git/commits{/sha}","comments_url":"https://api.github.com/repos/flutter/flutter/comments{/number}","issue_comment_url":"https://api.github.com/repos/flutter/flutter/issues/comments{/number}","contents_url":"https://api.github.com/repos/flutter/flutter/contents/{+path}","compare_url":"https://api.github.com/repos/flutter/flutter/compare/{base}...{head}","merges_url":"https://api.github.com/repos/flutter/flutter/merges","archive_url":"https://api.github.com/repos/flutter/flutter/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/flutter/flutter/downloads","issues_url":"https://api.github.com/repos/flutter/flutter/issues{/number}","pulls_url":"https://api.github.com/repos/flutter/flutter/pulls{/number}","milestones_url":"https://api.github.com/repos/flutter/flutter/milestones{/number}","notifications_url":"https://api.github.com/repos/flutter/flutter/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/flutter/flutter/labels{/name}","releases_url":"https://api.github.com/repos/flutter/flutter/releases{/id}","deployments_url":"https://api.github.com/repos/flutter/flutter/deployments","created_at":"2015-03-06T22:54:58Z","updated_at":"2016-12-05T12:39:46Z","pushed_at":"2016-12-05T17:09:21Z","git_url":"git://github.com/flutter/flutter.git","ssh_url":"git@github.com:flutter/flutter.git","clone_url":"https://github.com/flutter/flutter.git","svn_url":"https://github.com/flutter/flutter","homepage":"https://flutter.io","size":117742,"stargazers_count":3632,"watchers_count":3632,"language":"Dart","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":392,"mirror_url":null,"open_issues_count":1461,"forks":392,"open_issues":1461,"watchers":3632,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/flutter/flutter/pulls/7146"},"html":{"href":"https://github.com/flutter/flutter/pull/7146"},"issue":{"href":"https://api.github.com/repos/flutter/flutter/issues/7146"},"comments":{"href":"https://api.github.com/repos/flutter/flutter/issues/7146/comments"},"review_comments":{"href":"https://api.github.com/repos/flutter/flutter/pulls/7146/comments"},"review_comment":{"href":"https://api.github.com/repos/flutter/flutter/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/flutter/flutter/pulls/7146/commits"},"statuses":{"href":"https://api.github.com/repos/flutter/flutter/statuses/574d516e6fb96f10059504916a2a909d7f34cd20"}}}}
{"actor":{"display_login":"cbracken"}}