Skip to content

Commit 928bb12

Browse files
authored
[tool] Consistent FakeProcessManager.run/runSync (#103947)
`FakeProcessManager` is a test-oriented implementation of `ProcessManager` that simulates launching processes and returning `ProcessResult` objects whose `exitCode`, `stdout`, `stderr` can be used to write platform-portable, hermetic tests that don't rely on actually launching processes from executables on disk. Its `run` and `runSync` methods provide asynchronous and synchronous variants of this functionality. Previously, the behaviour of `run` and `runSync` were inconsistent with regards to the treatment of the `stdoutEncoding` (similarly, `stderrEncoding`) parameters: `run`: * if the encoding was null, `ProcessResult.stdout` was returned as a String in UTF-8 encoding. This was incorrect. The behaviour as specified in `ProcessResult.stdout` is that in this case, a raw `List<int>` should be returned. * If the encoding was unspecified, `ProcessResult.stdout` was returned as a `String` in the `io.systemEncoding` encoding. This was correct. * If the encoding was non-null, `ProcessResult.stdout` was returned as a `String` in the specified encoding. This was correct. `runSync`: * if the encoding was null, `ProcessResult.stdout` was returned as a `List<int>` in UTF-8 encoding. This was incorrect. The behaviour as specified in `ProcessResult.stdout` is that in this case, a raw `List<int>` should be returned. * If the encoding was unspecified, `ProcessResult.stdout` was returned as `List<int>` in UTF-8 encoding. This was incorrect. The behaviour as specified in `ProcessResult.stdout` is that in this case, a String a `String` in the `io.systemEncoding` encoding should be returned. * if the encoding was non-null, `ProcessResult.stdout` was returned as a `String` in unknown (but probably UTF-8) encoding. This was incorrect. The behaviour as specified in `ProcessResult.stdout` is that in this case, a `String` in the specified encoding should be returned. `_FakeProcess`, from which we obtain the fake stdout and stderr values now holds these fields as raw `List<int>` of bytes rather than as `String`s. It is up to the user to supply values that can be decoded with the encoding passed to `run`/`runAsync`. `run` and `runAsync` have been updated to set stdout (likewise, stderr) as specified in the `ProcessResult` documentation. This is pre-factoring for #102451, in which the tool throws an exception when processing the JSON output from stdout of the `vswhere.exe` tool, whose output was found to include the `U+FFFD` Unicode replacement character during UTF-8 decoding, which triggers a `toolExit` exception when decoded using our [Utf8Decoder][decoder] configured with `reportErrors` = true. Because `FakeProcessManager.runAsync` did not previously invoke `utf8.decode` on its output (behaviour which differs from the non-fake implementation), it was impossible to write tests to verify the fix. Ref: https://api.flutter.dev/flutter/dart-io/ProcessResult/stdout.html Issue: flutter/flutter#102451 [decoder]: https://github.com/flutter/flutter/blob/fd312f1ccff909fde28d2247a489bf210bbc6c48/packages/flutter_tools/lib/src/convert.dart#L51-L60
1 parent 1994027 commit 928bb12

File tree

2 files changed

+21
-21
lines changed

2 files changed

+21
-21
lines changed

packages/flutter_tools/test/general.shard/ios/ios_device_port_forwarder_test.dart

+1-3
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5-
// @dart = 2.8
6-
75
import 'package:flutter_tools/src/base/logger.dart';
86
import 'package:flutter_tools/src/ios/devices.dart';
97

@@ -25,8 +23,8 @@ void main() {
2523
// the FakeCommands below expect an exitCode of 0.
2624
const FakeCommand(
2725
command: <String>['iproxy', '12345:456', '--udid', '1234'],
28-
stdout: null, // no stdout indicates failure.
2926
environment: kDyLdLibEntry,
27+
// Empty stdout indicates failure.
3028
),
3129
const FakeCommand(
3230
command: <String>['iproxy', '12346:456', '--udid', '1234'],

packages/flutter_tools/test/src/fake_process_manager.dart

+20-18
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ class FakeCommand {
100100
/// If provided, this exception will be thrown when the fake command is run.
101101
final Object? exception;
102102

103-
/// Indicates that output will only be emitted after the `exitCode` [Future]
104-
/// on [io.Process] completes.
103+
/// When true, stdout and stderr will only be emitted after the `exitCode`
104+
/// [Future] on [io.Process] completes.
105105
final bool outputFollowsExit;
106106

107107
void _matches(
@@ -141,24 +141,26 @@ class _FakeProcess implements io.Process {
141141
}),
142142
stdin = stdin ?? IOSink(StreamController<List<int>>().sink)
143143
{
144-
if (_stderr == null) {
144+
if (_stderr.isEmpty) {
145145
stderr = const Stream<List<int>>.empty();
146146
} else if (outputFollowsExit) {
147+
// Wait for the process to exit before emitting stderr.
147148
stderr = Stream<List<int>>.fromFuture(exitCode.then((_) {
148-
return Future<List<int>>(() => utf8.encode(_stderr));
149+
return Future<List<int>>(() => _stderr);
149150
}));
150151
} else {
151-
stderr = Stream<List<int>>.value(utf8.encode(_stderr));
152+
stderr = Stream<List<int>>.value(_stderr);
152153
}
153154

154-
if (_stdout == null) {
155+
if (_stdout.isEmpty) {
155156
stdout = const Stream<List<int>>.empty();
156157
} else if (outputFollowsExit) {
158+
// Wait for the process to exit before emitting stdout.
157159
stdout = Stream<List<int>>.fromFuture(exitCode.then((_) {
158-
return Future<List<int>>(() => utf8.encode(_stdout));
160+
return Future<List<int>>(() => _stdout);
159161
}));
160162
} else {
161-
stdout = Stream<List<int>>.value(utf8.encode(_stdout));
163+
stdout = Stream<List<int>>.value(_stdout);
162164
}
163165
}
164166

@@ -171,7 +173,7 @@ class _FakeProcess implements io.Process {
171173
@override
172174
final int pid;
173175

174-
final String _stderr;
176+
final List<int> _stderr;
175177

176178
@override
177179
late final Stream<List<int>> stderr;
@@ -182,7 +184,7 @@ class _FakeProcess implements io.Process {
182184
@override
183185
late final Stream<List<int>> stdout;
184186

185-
final String _stdout;
187+
final List<int> _stdout;
186188

187189
@override
188190
bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) {
@@ -268,9 +270,9 @@ abstract class FakeProcessManager implements ProcessManager {
268270
fakeCommand.exitCode,
269271
fakeCommand.duration,
270272
_pid,
271-
fakeCommand.stderr,
273+
encoding?.encode(fakeCommand.stderr) ?? fakeCommand.stderr.codeUnits,
272274
fakeCommand.stdin,
273-
fakeCommand.stdout,
275+
encoding?.encode(fakeCommand.stdout) ?? fakeCommand.stdout.codeUnits,
274276
fakeCommand.completer,
275277
fakeCommand.outputFollowsExit,
276278
);
@@ -310,8 +312,8 @@ abstract class FakeProcessManager implements ProcessManager {
310312
return io.ProcessResult(
311313
process.pid,
312314
process._exitCode,
313-
stdoutEncoding == null ? process.stdout : await stdoutEncoding.decodeStream(process.stdout),
314-
stderrEncoding == null ? process.stderr : await stderrEncoding.decodeStream(process.stderr),
315+
stdoutEncoding == null ? process._stdout : await stdoutEncoding.decodeStream(process.stdout),
316+
stderrEncoding == null ? process._stderr : await stderrEncoding.decodeStream(process.stderr),
315317
);
316318
}
317319

@@ -322,15 +324,15 @@ abstract class FakeProcessManager implements ProcessManager {
322324
Map<String, String>? environment,
323325
bool includeParentEnvironment = true, // ignored
324326
bool runInShell = false, // ignored
325-
Encoding? stdoutEncoding = io.systemEncoding, // actual encoder is ignored
326-
Encoding? stderrEncoding = io.systemEncoding, // actual encoder is ignored
327+
Encoding? stdoutEncoding = io.systemEncoding,
328+
Encoding? stderrEncoding = io.systemEncoding,
327329
}) {
328330
final _FakeProcess process = _runCommand(command.cast<String>(), workingDirectory, environment, stdoutEncoding);
329331
return io.ProcessResult(
330332
process.pid,
331333
process._exitCode,
332-
stdoutEncoding == null ? utf8.encode(process._stdout) : process._stdout,
333-
stderrEncoding == null ? utf8.encode(process._stderr) : process._stderr,
334+
stdoutEncoding == null ? process._stdout : stdoutEncoding.decode(process._stdout),
335+
stderrEncoding == null ? process._stderr : stderrEncoding.decode(process._stderr),
334336
);
335337
}
336338

0 commit comments

Comments
 (0)