Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
03d163ce
Commit
03d163ce
authored
Jan 23, 2017
by
Todd Volkert
Committed by
GitHub
Jan 23, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update tools to use package:process (#7590)
parent
24f1b2ee
Changes
14
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
79 additions
and
1095 deletions
+79
-1095
fuchsia_builder.dart
packages/flutter_tools/bin/fuchsia_builder.dart
+2
-2
executable.dart
packages/flutter_tools/lib/executable.dart
+2
-2
process_manager.dart
packages/flutter_tools/lib/src/base/process_manager.dart
+64
-831
flutter_command_runner.dart
.../flutter_tools/lib/src/runner/flutter_command_runner.dart
+7
-20
pubspec.yaml
packages/flutter_tools/pubspec.yaml
+1
-0
all.dart
packages/flutter_tools/test/all.dart
+0
-2
001.sing.100.stderr
...ools/test/data/process_manager/replay/001.sing.100.stderr
+0
-1
001.sing.100.stdout
...ools/test/data/process_manager/replay/001.sing.100.stdout
+0
-2
002.dance.101.stderr
...ols/test/data/process_manager/replay/002.dance.101.stderr
+0
-1
002.dance.101.stdout
...ols/test/data/process_manager/replay/002.dance.101.stdout
+0
-0
MANIFEST.txt
...utter_tools/test/data/process_manager/replay/MANIFEST.txt
+0
-23
process_manager_test.dart
packages/flutter_tools/test/process_manager_test.dart
+0
-208
common.dart
packages/flutter_tools/test/src/common.dart
+1
-1
context.dart
packages/flutter_tools/test/src/context.dart
+2
-2
No files found.
packages/flutter_tools/bin/fuchsia_builder.dart
View file @
03d163ce
...
...
@@ -5,6 +5,7 @@
import
'dart:async'
;
import
'package:args/args.dart'
;
import
'package:process/process.dart'
;
import
'../lib/src/base/common.dart'
;
import
'../lib/src/base/config.dart'
;
...
...
@@ -13,7 +14,6 @@ import '../lib/src/base/file_system.dart';
import
'../lib/src/base/io.dart'
;
import
'../lib/src/base/logger.dart'
;
import
'../lib/src/base/os.dart'
;
import
'../lib/src/base/process_manager.dart'
;
import
'../lib/src/cache.dart'
;
import
'../lib/src/flx.dart'
;
import
'../lib/src/globals.dart'
;
...
...
@@ -39,7 +39,7 @@ Future<Null> main(List<String> args) async {
executableContext
.
runInZone
(()
{
// Initialize the context with some defaults.
context
.
putIfAbsent
(
FileSystem
,
()
=>
new
LocalFileSystem
());
context
.
putIfAbsent
(
ProcessManager
,
()
=>
new
ProcessManager
());
context
.
putIfAbsent
(
ProcessManager
,
()
=>
new
Local
ProcessManager
());
context
.
putIfAbsent
(
Logger
,
()
=>
new
StdoutLogger
());
context
.
putIfAbsent
(
Cache
,
()
=>
new
Cache
());
context
.
putIfAbsent
(
Config
,
()
=>
new
Config
());
...
...
packages/flutter_tools/lib/executable.dart
View file @
03d163ce
...
...
@@ -5,6 +5,7 @@
import
'dart:async'
;
import
'package:args/command_runner.dart'
;
import
'package:process/process.dart'
;
import
'package:stack_trace/stack_trace.dart'
;
import
'src/base/common.dart'
;
...
...
@@ -15,7 +16,6 @@ import 'src/base/io.dart';
import
'src/base/logger.dart'
;
import
'src/base/os.dart'
;
import
'src/base/process.dart'
;
import
'src/base/process_manager.dart'
;
import
'src/base/utils.dart'
;
import
'src/cache.dart'
;
import
'src/commands/analyze.dart'
;
...
...
@@ -103,7 +103,7 @@ Future<Null> main(List<String> args) async {
// Seed these context entries first since others depend on them
context
.
putIfAbsent
(
FileSystem
,
()
=>
new
LocalFileSystem
());
context
.
putIfAbsent
(
ProcessManager
,
()
=>
new
ProcessManager
());
context
.
putIfAbsent
(
ProcessManager
,
()
=>
new
Local
ProcessManager
());
context
.
putIfAbsent
(
Logger
,
()
=>
new
StdoutLogger
());
// Order-independent context entries
...
...
packages/flutter_tools/lib/src/base/process_manager.dart
View file @
03d163ce
...
...
@@ -3,849 +3,82 @@
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:convert'
;
import
'package:archive/archive.dart'
;
import
'package:intl/intl.dart'
;
import
'package:path/path.dart'
as
path
;
import
'package:process/process.dart'
;
import
'package:process/record_replay.dart'
;
import
'common.dart'
;
import
'context.dart'
;
import
'file_system.dart'
hide
IOSink
;
import
'io.dart'
;
import
'os.dart'
;
import
'file_system.dart'
;
import
'process.dart'
;
/// The active process manager.
ProcessManager
get
processManager
=>
context
[
ProcessManager
];
const
String
_kManifestName
=
'MANIFEST.txt'
;
bool
_areListsEqual
<
T
>(
List
<
T
>
list1
,
List
<
T
>
list2
)
{
int
i
=
0
;
return
list1
!=
null
&&
list2
!=
null
&&
list1
.
length
==
list2
.
length
&&
list1
.
every
((
dynamic
element
)
=>
element
==
list2
[
i
++]);
}
/// A class that manages the creation of operating system processes. This
/// provides a lightweight wrapper around the underlying [Process] static
/// methods to allow the implementation of these methods to be mocked out or
/// decorated for testing or debugging purposes.
class
ProcessManager
{
Future
<
Process
>
start
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
ProcessStartMode
mode:
ProcessStartMode
.
NORMAL
,
})
{
return
Process
.
start
(
executable
,
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
mode:
mode
,
);
}
Future
<
ProcessResult
>
run
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
Encoding
stdoutEncoding:
SYSTEM_ENCODING
,
Encoding
stderrEncoding:
SYSTEM_ENCODING
,
})
{
return
Process
.
run
(
executable
,
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
,
);
}
ProcessResult
runSync
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
Encoding
stdoutEncoding:
SYSTEM_ENCODING
,
Encoding
stderrEncoding:
SYSTEM_ENCODING
,
})
{
return
Process
.
runSync
(
executable
,
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
,
);
}
bool
killPid
(
int
pid
,
[
ProcessSignal
signal
=
ProcessSignal
.
SIGTERM
])
{
return
Process
.
killPid
(
pid
,
signal
);
}
}
/// A [ProcessManager] implementation that decorates the standard behavior by
/// recording all process invocation activity (including the stdout and stderr
/// of the associated processes) and serializing that recording to a ZIP file
/// when the Flutter tools process exits.
class
RecordingProcessManager
implements
ProcessManager
{
static
const
String
kDefaultRecordTo
=
'recording.zip'
;
static
const
List
<
String
>
_kSkippableExecutables
=
const
<
String
>[
'env'
,
'xcrun'
,
];
final
String
_recordTo
;
final
ProcessManager
_delegate
=
new
ProcessManager
();
final
Directory
_tmpDir
=
fs
.
systemTempDirectory
.
createTempSync
(
'flutter_tools_'
);
final
List
<
Map
<
String
,
dynamic
>>
_manifest
=
<
Map
<
String
,
dynamic
>>[];
final
Map
<
int
,
Future
<
int
>>
_runningProcesses
=
<
int
,
Future
<
int
>>{};
/// Constructs a new `RecordingProcessManager` that will record all process
/// invocations and serialize them to the a ZIP file at the specified
/// [recordTo] location.
///
/// If [recordTo] is a directory, a ZIP file named
/// [kDefaultRecordTo](`recording.zip`) will be created in the specified
/// directory.
///
/// If [recordTo] is a file (or doesn't exist), it is taken to be the name
/// of the ZIP file that will be created, and the containing folder will be
/// created as needed.
RecordingProcessManager
(
this
.
_recordTo
)
{
addShutdownHook
(
_onShutdown
);
}
@override
Future
<
Process
>
start
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
ProcessStartMode
mode:
ProcessStartMode
.
NORMAL
,
})
async
{
Process
process
=
await
_delegate
.
start
(
executable
,
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
mode:
mode
,
);
String
basename
=
_getBasename
(
process
.
pid
,
executable
,
arguments
);
Map
<
String
,
dynamic
>
manifestEntry
=
_createManifestEntry
(
pid:
process
.
pid
,
basename:
basename
,
executable:
executable
,
arguments:
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
mode:
mode
,
);
_manifest
.
add
(
manifestEntry
);
_RecordingProcess
result
=
new
_RecordingProcess
(
manager:
this
,
basename:
basename
,
delegate:
process
,
);
await
result
.
startRecording
();
_runningProcesses
[
process
.
pid
]
=
result
.
exitCode
.
then
((
int
exitCode
)
{
_runningProcesses
.
remove
(
process
.
pid
);
manifestEntry
[
'exitCode'
]
=
exitCode
;
});
return
result
;
}
@override
Future
<
ProcessResult
>
run
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
Encoding
stdoutEncoding:
SYSTEM_ENCODING
,
Encoding
stderrEncoding:
SYSTEM_ENCODING
,
})
async
{
ProcessResult
result
=
await
_delegate
.
run
(
executable
,
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
,
);
String
basename
=
_getBasename
(
result
.
pid
,
executable
,
arguments
);
_manifest
.
add
(
_createManifestEntry
(
pid:
result
.
pid
,
basename:
basename
,
executable:
executable
,
arguments:
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
,
exitCode:
result
.
exitCode
,
));
await
_recordData
(
result
.
stdout
,
stdoutEncoding
,
'
$basename
.stdout'
);
await
_recordData
(
result
.
stderr
,
stderrEncoding
,
'
$basename
.stderr'
);
return
result
;
}
Future
<
Null
>
_recordData
(
dynamic
data
,
Encoding
encoding
,
String
basename
)
async
{
String
path
=
'
${_tmpDir.path}
/
$basename
'
;
File
file
=
await
fs
.
file
(
path
).
create
();
RandomAccessFile
recording
=
await
file
.
open
(
mode:
FileMode
.
WRITE
);
try
{
if
(
encoding
==
null
)
await
recording
.
writeFrom
(
data
);
else
await
recording
.
writeString
(
data
,
encoding:
encoding
);
await
recording
.
flush
();
}
finally
{
await
recording
.
close
();
}
}
@override
ProcessResult
runSync
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
Encoding
stdoutEncoding:
SYSTEM_ENCODING
,
Encoding
stderrEncoding:
SYSTEM_ENCODING
,
})
{
ProcessResult
result
=
_delegate
.
runSync
(
executable
,
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
,
);
String
basename
=
_getBasename
(
result
.
pid
,
executable
,
arguments
);
_manifest
.
add
(
_createManifestEntry
(
pid:
result
.
pid
,
basename:
basename
,
executable:
executable
,
arguments:
arguments
,
workingDirectory:
workingDirectory
,
environment:
environment
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
,
exitCode:
result
.
exitCode
,
));
_recordDataSync
(
result
.
stdout
,
stdoutEncoding
,
'
$basename
.stdout'
);
_recordDataSync
(
result
.
stderr
,
stderrEncoding
,
'
$basename
.stderr'
);
return
result
;
}
void
_recordDataSync
(
dynamic
data
,
Encoding
encoding
,
String
basename
)
{
String
path
=
'
${_tmpDir.path}
/
$basename
'
;
File
file
=
fs
.
file
(
path
)..
createSync
();
RandomAccessFile
recording
=
file
.
openSync
(
mode:
FileMode
.
WRITE
);
try
{
if
(
encoding
==
null
)
recording
.
writeFromSync
(
data
);
else
recording
.
writeStringSync
(
data
,
encoding:
encoding
);
recording
.
flushSync
();
}
finally
{
recording
.
closeSync
();
}
}
@override
bool
killPid
(
int
pid
,
[
ProcessSignal
signal
=
ProcessSignal
.
SIGTERM
])
{
return
_delegate
.
killPid
(
pid
,
signal
);
}
/// Creates a JSON-encodable manifest entry representing the specified
/// process invocation.
Map
<
String
,
dynamic
>
_createManifestEntry
({
int
pid
,
String
basename
,
String
executable
,
List
<
String
>
arguments
,
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
ProcessStartMode
mode
,
Encoding
stdoutEncoding
,
Encoding
stderrEncoding
,
int
exitCode
,
})
{
return
new
_ManifestEntryBuilder
()
.
add
(
'pid'
,
pid
)
.
add
(
'basename'
,
basename
)
.
add
(
'executable'
,
executable
)
.
add
(
'arguments'
,
arguments
)
.
add
(
'workingDirectory'
,
workingDirectory
)
.
add
(
'environment'
,
environment
)
.
add
(
'mode'
,
mode
,
()
=>
mode
.
toString
())
.
add
(
'stdoutEncoding'
,
stdoutEncoding
,
()
=>
stdoutEncoding
.
name
)
.
add
(
'stderrEncoding'
,
stderrEncoding
,
()
=>
stderrEncoding
.
name
)
.
add
(
'exitCode'
,
exitCode
)
.
entry
;
}
/// Returns a human-readable identifier for the specified executable.
String
_getBasename
(
int
pid
,
String
executable
,
List
<
String
>
arguments
)
{
String
index
=
new
NumberFormat
(
'000'
).
format
(
_manifest
.
length
);
String
identifier
=
path
.
basename
(
executable
);
if
(
_kSkippableExecutables
.
contains
(
identifier
)
&&
arguments
!=
null
&&
arguments
.
isNotEmpty
)
{
identifier
=
path
.
basename
(
arguments
.
first
);
}
return
'
$index
.
$identifier
.
$pid
'
;
}
/// Invoked when the outermost executable process is about to shutdown
/// safely. This saves our recording to a ZIP file at the location specified
/// in the [new RecordingProcessManager] constructor.
Future
<
Null
>
_onShutdown
()
async
{
await
_waitForRunningProcessesToExit
();
await
_writeManifestToDisk
();
await
_saveRecording
();
await
_tmpDir
.
delete
(
recursive:
true
);
}
/// Waits for all running processes to exit, and records their exit codes in
/// the process manifest. Any process that doesn't exit in a timely fashion
/// will have a `"daemon"` marker added to its manifest and be signalled with
/// `SIGTERM`. If such processes *still* don't exit in a timely fashion after
/// being signalled, they'll have a `"notResponding"` marker added to their
/// manifest.
Future
<
Null
>
_waitForRunningProcessesToExit
()
async
{
await
_waitForRunningProcessesToExitWithTimeout
(
onTimeout:
(
int
pid
,
Map
<
String
,
dynamic
>
manifestEntry
)
{
manifestEntry
[
'daemon'
]
=
true
;
Process
.
killPid
(
pid
);
});
// Now that we explicitly signalled the processes that timed out asking
// them to shutdown, wait one more time for those processes to exit.
await
_waitForRunningProcessesToExitWithTimeout
(
onTimeout:
(
int
pid
,
Map
<
String
,
dynamic
>
manifestEntry
)
{
manifestEntry
[
'notResponding'
]
=
true
;
});
}
Future
<
Null
>
_waitForRunningProcessesToExitWithTimeout
({
void
onTimeout
(
int
pid
,
Map
<
String
,
dynamic
>
manifestEntry
),
})
async
{
await
Future
.
wait
(
new
List
<
Future
<
int
>>.
from
(
_runningProcesses
.
values
))
.
timeout
(
const
Duration
(
milliseconds:
20
),
onTimeout:
()
{
_runningProcesses
.
forEach
((
int
pid
,
Future
<
int
>
future
)
{
Map
<
String
,
dynamic
>
manifestEntry
=
_manifest
.
firstWhere
((
Map
<
String
,
dynamic
>
entry
)
=>
entry
[
'pid'
]
==
pid
);
onTimeout
(
pid
,
manifestEntry
);
});
});
}
/// Writes our process invocation manifest to disk in our temp folder.
Future
<
Null
>
_writeManifestToDisk
()
async
{
JsonEncoder
encoder
=
new
JsonEncoder
.
withIndent
(
' '
);
String
encodedManifest
=
encoder
.
convert
(
_manifest
);
File
manifestFile
=
await
fs
.
file
(
'
${_tmpDir.path}
/
$_kManifestName
'
).
create
();
await
manifestFile
.
writeAsString
(
encodedManifest
,
flush:
true
);
}
/// Saves our recording to a ZIP file at the specified location.
Future
<
Null
>
_saveRecording
()
async
{
File
zipFile
=
await
_createZipFile
();
List
<
int
>
zipData
=
await
_getRecordingZipBytes
();
await
zipFile
.
writeAsBytes
(
zipData
);
}
/// Creates our recording ZIP file at the location specified
/// in the [new RecordingProcessManager] constructor.
Future
<
File
>
_createZipFile
()
async
{
File
zipFile
;
String
recordTo
=
_recordTo
??
fs
.
currentDirectory
.
path
;
if
(
fs
.
isDirectorySync
(
recordTo
))
{
zipFile
=
fs
.
file
(
'
$recordTo
/
$kDefaultRecordTo
'
);
}
else
{
zipFile
=
fs
.
file
(
recordTo
);
await
fs
.
directory
(
path
.
dirname
(
zipFile
.
path
)).
create
(
recursive:
true
);
}
// Resolve collisions.
String
basename
=
path
.
basename
(
zipFile
.
path
);
for
(
int
i
=
1
;
zipFile
.
existsSync
();
i
++)
{
assert
(
fs
.
isFileSync
(
zipFile
.
path
));
String
disambiguator
=
new
NumberFormat
(
'00'
).
format
(
i
);
String
newBasename
=
basename
;
if
(
basename
.
contains
(
'.'
))
{
List
<
String
>
parts
=
basename
.
split
(
'.'
);
parts
[
parts
.
length
-
2
]
+=
'-
$disambiguator
'
;
newBasename
=
parts
.
join
(
'.'
);
}
else
{
newBasename
+=
'-
$disambiguator
'
;
}
zipFile
=
fs
.
file
(
path
.
join
(
path
.
dirname
(
zipFile
.
path
),
newBasename
));
}
return
await
zipFile
.
create
();
}
/// Gets the bytes of our ZIP file recording.
Future
<
List
<
int
>>
_getRecordingZipBytes
()
async
{
Archive
archive
=
new
Archive
();
Stream
<
FileSystemEntity
>
files
=
_tmpDir
.
list
(
recursive:
true
)
.
where
((
FileSystemEntity
entity
)
=>
fs
.
isFileSync
(
entity
.
path
));
List
<
Future
<
dynamic
>>
addAllFilesToArchive
=
<
Future
<
dynamic
>>[];
await
files
.
forEach
((
FileSystemEntity
entity
)
{
File
file
=
entity
;
Future
<
dynamic
>
readAsBytes
=
file
.
readAsBytes
();
addAllFilesToArchive
.
add
(
readAsBytes
.
then
<
Null
>((
List
<
int
>
data
)
{
archive
.
addFile
(
new
ArchiveFile
.
noCompress
(
path
.
basename
(
file
.
path
),
data
.
length
,
data
)
);
}));
});
await
Future
.
wait
<
dynamic
>(
addAllFilesToArchive
);
return
new
ZipEncoder
().
encode
(
archive
);
}
}
/// A lightweight class that provides a builder pattern for building a
/// manifest entry.
class
_ManifestEntryBuilder
{
Map
<
String
,
dynamic
>
entry
=
<
String
,
dynamic
>{};
/// Adds the specified key/value pair to the manifest entry iff the value
/// is non-null. If [jsonValue] is specified, its value will be used instead
/// of the raw value.
_ManifestEntryBuilder
add
(
String
name
,
dynamic
value
,
[
dynamic
jsonValue
()])
{
if
(
value
!=
null
)
entry
[
name
]
=
jsonValue
==
null
?
value
:
jsonValue
();
return
this
;
}
}
/// A [Process] implementation that records `stdout` and `stderr` stream events
/// to disk before forwarding them on to the underlying stream listener.
class
_RecordingProcess
implements
Process
{
final
Process
delegate
;
final
String
basename
;
final
RecordingProcessManager
manager
;
bool
_started
=
false
;
StreamController
<
List
<
int
>>
_stdoutController
=
new
StreamController
<
List
<
int
>>();
StreamController
<
List
<
int
>>
_stderrController
=
new
StreamController
<
List
<
int
>>();
_RecordingProcess
({
this
.
manager
,
this
.
basename
,
this
.
delegate
});
Future
<
Null
>
startRecording
()
async
{
assert
(!
_started
);
_started
=
true
;
await
Future
.
wait
(<
Future
<
Null
>>[
_recordStream
(
delegate
.
stdout
,
_stdoutController
,
'stdout'
),
_recordStream
(
delegate
.
stderr
,
_stderrController
,
'stderr'
),
]);
}
Future
<
Null
>
_recordStream
(
Stream
<
List
<
int
>>
stream
,
StreamController
<
List
<
int
>>
controller
,
String
suffix
,
)
async
{
String
path
=
'
${manager._tmpDir.path}
/
$basename
.
$suffix
'
;
File
file
=
await
fs
.
file
(
path
).
create
();
RandomAccessFile
recording
=
await
file
.
open
(
mode:
FileMode
.
WRITE
);
stream
.
listen
(
(
List
<
int
>
data
)
{
// Write synchronously to guarantee that the order of data
// within our recording is preserved across stream notifications.
recording
.
writeFromSync
(
data
);
// Flush immediately so that if the program crashes, forensic
// data from the recording won't be lost.
recording
.
flushSync
();
controller
.
add
(
data
);
},
onError:
(
dynamic
error
,
StackTrace
stackTrace
)
{
recording
.
closeSync
();
controller
.
addError
(
error
,
stackTrace
);
},
onDone:
()
{
recording
.
closeSync
();
controller
.
close
();
},
);
}
@override
Future
<
int
>
get
exitCode
=>
delegate
.
exitCode
;
// TODO(tvolkert): Remove this once the dart sdk in both the target and
// the host have picked up dart-lang/sdk@e5a16b1
@override
// ignore: OVERRIDE_ON_NON_OVERRIDING_SETTER
set
exitCode
(
Future
<
int
>
exitCode
)
=>
throw
new
UnsupportedError
(
'set exitCode'
);
@override
Stream
<
List
<
int
>>
get
stdout
{
assert
(
_started
);
return
_stdoutController
.
stream
;
}
@override
Stream
<
List
<
int
>>
get
stderr
{
assert
(
_started
);
return
_stderrController
.
stream
;
}
@override
IOSink
get
stdin
{
// We don't currently support recording `stdin`.
return
delegate
.
stdin
;
}
@override
int
get
pid
=>
delegate
.
pid
;
@override
bool
kill
([
ProcessSignal
signal
=
ProcessSignal
.
SIGTERM
])
=>
delegate
.
kill
(
signal
);
}
/// A [ProcessManager] implementation that mocks out all process invocations
/// by replaying a previously-recorded series of invocations, throwing an
/// exception if the requested invocations substantively differ in any way
/// from those in the recording.
/// Enables recording of process invocation activity to the specified location.
///
///
Recordings are expected to be of the form produced by
///
[RecordingProcessManager]. Namely, this includes:
///
This sets the [active process manager](processManager) to one that records
///
all process activity before delegating to a [LocalProcessManager].
///
/// - a [_kManifestName](manifest file) encoded as UTF-8 JSON that lists all
/// invocations in order, along with the following metadata for each
/// invocation:
/// - `pid` (required): The process id integer.
/// - `basename` (required): A string specifying the base filename from which
/// the incovation's `stdout` and `stderr` files can be located.
/// - `executable` (required): A string specifying the path to the executable
/// command that kicked off the process.
/// - `arguments` (required): A list of strings that were passed as arguments
/// to the executable.
/// - `workingDirectory` (required): The current working directory from which
/// the process was spawned.
/// - `environment` (required): A map from string environment variable keys
/// to their corresponding string values.
/// - `mode` (optional): A string specifying the [ProcessStartMode].
/// - `stdoutEncoding` (optional): The name of the encoding scheme that was
/// used in the `stdout` file. If unspecified, then the file was written
/// as binary data.
/// - `stderrEncoding` (optional): The name of the encoding scheme that was
/// used in the `stderr` file. If unspecified, then the file was written
/// as binary data.
/// - `exitCode` (required): The exit code of the process, or null if the
/// process was not responding.
/// - `daemon` (optional): A boolean indicating that the process is to stay
/// resident during the entire lifetime of the master Flutter tools process.
/// - a `stdout` file for each process invocation. The location of this file
/// can be derived from the `basename` manifest property like so:
/// `'$basename.stdout'`.
/// - a `stderr` file for each process invocation. The location of this file
/// can be derived from the `basename` manifest property like so:
/// `'$basename.stderr'`.
class
ReplayProcessManager
implements
ProcessManager
{
final
List
<
Map
<
String
,
dynamic
>>
_manifest
;
final
Directory
_dir
;
ReplayProcessManager
.
_
(
this
.
_manifest
,
this
.
_dir
);
/// Creates a new `ReplayProcessManager` capable of replaying a recording at
/// the specified location.
///
/// If [location] represents a file, it will be treated like a recording
/// ZIP file. If it points to a directory, it will be treated like an
/// unzipped recording. If [location] points to a non-existent file or
/// directory, an [ArgumentError] will be thrown.
static
Future
<
ReplayProcessManager
>
create
(
String
location
)
async
{
Directory
dir
;
switch
(
fs
.
typeSync
(
location
))
{
/// [location] must either represent a valid, empty directory or a non-existent
/// file system entity, in which case a directory will be created at that path.
/// Process invocation activity will be serialized to opaque files in that
/// directory. The resulting (populated) directory will be suitable for use
/// with [enableReplayProcessManager].
void
enableRecordingProcessManager
(
String
location
)
{
if
(
location
.
isEmpty
)
throwToolExit
(
'record-to location not specified'
);
switch
(
fs
.
typeSync
(
location
,
followLinks:
false
))
{
case
FileSystemEntityType
.
FILE
:
dir
=
await
fs
.
systemTempDirectory
.
createTemp
(
'flutter_tools_'
);
os
.
unzip
(
fs
.
file
(
location
),
dir
);
addShutdownHook
(()
async
{
await
dir
.
delete
(
recursive:
true
);
});
case
FileSystemEntityType
.
LINK
:
throwToolExit
(
'record-to location must reference a directory'
);
break
;
case
FileSystemEntityType
.
DIRECTORY
:
dir
=
fs
.
directory
(
location
);
if
(
fs
.
directory
(
location
).
listSync
(
followLinks:
false
).
isNotEmpty
)
throwToolExit
(
'record-to directory must be empty'
);
break
;
case
FileSystemEntityType
.
NOT_FOUND
:
throw
new
ArgumentError
.
value
(
location
,
'location'
,
'Does not exist'
);
}
File
manifestFile
=
fs
.
file
(
path
.
join
(
dir
.
path
,
_kManifestName
));
if
(!
manifestFile
.
existsSync
())
{
// We use the existence of the manifest as a proxy for this being a
// valid replay directory. Namely, we don't validate the structure of the
// JSON within the manifest, and we don't validate the existence of
// all stdout and stderr files referenced in the manifest.
throw
new
ArgumentError
.
value
(
location
,
'location'
,
'Does not represent a valid recording (it does not '
'contain
$_kManifestName
).'
);
}
String
content
=
await
manifestFile
.
readAsString
();
try
{
List
<
Map
<
String
,
dynamic
>>
manifest
=
new
JsonDecoder
().
convert
(
content
);
return
new
ReplayProcessManager
.
_
(
manifest
,
dir
);
}
on
FormatException
catch
(
e
)
{
throw
new
ArgumentError
(
'
$_kManifestName
is not a valid JSON file:
$e
'
);
}
}
@override
Future
<
Process
>
start
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
ProcessStartMode
mode:
ProcessStartMode
.
NORMAL
,
})
async
{
Map
<
String
,
dynamic
>
entry
=
_popEntry
(
executable
,
arguments
,
mode:
mode
);
_ReplayProcessResult
result
=
await
_ReplayProcessResult
.
create
(
executable
,
arguments
,
_dir
,
entry
);
return
result
.
asProcess
(
entry
[
'daemon'
]
??
false
);
}
@override
Future
<
ProcessResult
>
run
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
Encoding
stdoutEncoding:
SYSTEM_ENCODING
,
Encoding
stderrEncoding:
SYSTEM_ENCODING
,
})
async
{
Map
<
String
,
dynamic
>
entry
=
_popEntry
(
executable
,
arguments
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
);
return
await
_ReplayProcessResult
.
create
(
executable
,
arguments
,
_dir
,
entry
);
}
@override
ProcessResult
runSync
(
String
executable
,
List
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
Encoding
stdoutEncoding:
SYSTEM_ENCODING
,
Encoding
stderrEncoding:
SYSTEM_ENCODING
,
})
{
Map
<
String
,
dynamic
>
entry
=
_popEntry
(
executable
,
arguments
,
stdoutEncoding:
stdoutEncoding
,
stderrEncoding:
stderrEncoding
);
return
_ReplayProcessResult
.
createSync
(
executable
,
arguments
,
_dir
,
entry
);
fs
.
directory
(
location
).
createSync
(
recursive:
true
);
}
Directory
dir
=
fs
.
directory
(
location
);
/// Finds and returns the next entry in the process manifest that matches
/// the specified process arguments. Once found, it marks the manifest entry
/// as having been invoked and thus not eligible for invocation again.
Map
<
String
,
dynamic
>
_popEntry
(
String
executable
,
List
<
String
>
arguments
,
{
ProcessStartMode
mode
,
Encoding
stdoutEncoding
,
Encoding
stderrEncoding
,
})
{
Map
<
String
,
dynamic
>
entry
=
_manifest
.
firstWhere
(
(
Map
<
String
,
dynamic
>
entry
)
{
// Ignore workingDirectory & environment, as they could
// yield false negatives.
return
entry
[
'executable'
]
==
executable
&&
_areListsEqual
(
entry
[
'arguments'
],
arguments
)
&&
entry
[
'mode'
]
==
mode
?.
toString
()
&&
entry
[
'stdoutEncoding'
]
==
stdoutEncoding
?.
name
&&
entry
[
'stderrEncoding'
]
==
stderrEncoding
?.
name
&&
!(
entry
[
'invoked'
]
??
false
);
},
orElse:
()
=>
null
,
);
if
(
entry
==
null
)
throw
new
ProcessException
(
executable
,
arguments
,
'No matching invocation found'
);
entry
[
'invoked'
]
=
true
;
return
entry
;
}
ProcessManager
delegate
=
new
LocalProcessManager
();
RecordingProcessManager
manager
=
new
RecordingProcessManager
(
delegate
,
dir
);
addShutdownHook
(()
async
{
await
manager
.
flush
(
finishRunningProcesses:
true
);
});
@override
bool
killPid
(
int
pid
,
[
ProcessSignal
signal
=
ProcessSignal
.
SIGTERM
])
{
throw
new
UnsupportedError
(
"
$runtimeType
.killPid() has not been implemented because at the time "
"of its writing, it wasn't needed. If you're hitting this error, you "
"should implement it."
);
}
context
.
setVariable
(
ProcessManager
,
manager
);
}
/// A [ProcessResult] implementation that derives its data from a recording
/// fragment.
class
_ReplayProcessResult
implements
ProcessResult
{
@override
final
int
pid
;
@override
final
int
exitCode
;
@override
final
dynamic
stdout
;
@override
final
dynamic
stderr
;
_ReplayProcessResult
.
_
({
this
.
pid
,
this
.
exitCode
,
this
.
stdout
,
this
.
stderr
});
static
Future
<
_ReplayProcessResult
>
create
(
String
executable
,
List
<
String
>
arguments
,
Directory
dir
,
Map
<
String
,
dynamic
>
entry
,
)
async
{
String
basePath
=
path
.
join
(
dir
.
path
,
entry
[
'basename'
]);
try
{
return
new
_ReplayProcessResult
.
_
(
pid:
entry
[
'pid'
],
exitCode:
entry
[
'exitCode'
],
stdout:
await
_getData
(
'
$basePath
.stdout'
,
entry
[
'stdoutEncoding'
]),
stderr:
await
_getData
(
'
$basePath
.stderr'
,
entry
[
'stderrEncoding'
]),
);
}
catch
(
e
)
{
throw
new
ProcessException
(
executable
,
arguments
,
e
.
toString
());
}
}
static
Future
<
dynamic
>
_getData
(
String
path
,
String
encoding
)
async
{
File
file
=
fs
.
file
(
path
);
return
encoding
==
null
?
await
file
.
readAsBytes
()
:
await
file
.
readAsString
(
encoding:
_getEncodingByName
(
encoding
));
}
static
_ReplayProcessResult
createSync
(
String
executable
,
List
<
String
>
arguments
,
Directory
dir
,
Map
<
String
,
dynamic
>
entry
,
)
{
String
basePath
=
path
.
join
(
dir
.
path
,
entry
[
'basename'
]);
/// Enables process invocation replay mode.
///
/// This sets the [active process manager](processManager) to one that replays
/// process activity from a previously recorded set of invocations.
///
/// [location] must represent a directory to which process activity has been
/// recorded (i.e. the result of having been previously passed to
/// [enableRecordingProcessManager]).
Future
<
Null
>
enableReplayProcessManager
(
String
location
)
async
{
if
(
location
.
isEmpty
)
throwToolExit
(
'replay-from location not specified'
);
Directory
dir
=
fs
.
directory
(
location
);
if
(!
dir
.
existsSync
())
throwToolExit
(
'replay-from location must reference a directory'
);
ProcessManager
manager
;
try
{
return
new
_ReplayProcessResult
.
_
(
pid:
entry
[
'pid'
],
exitCode:
entry
[
'exitCode'
],
stdout:
_getDataSync
(
'
$basePath
.stdout'
,
entry
[
'stdoutEncoding'
]),
stderr:
_getDataSync
(
'
$basePath
.stderr'
,
entry
[
'stderrEncoding'
]),
);
}
catch
(
e
)
{
throw
new
ProcessException
(
executable
,
arguments
,
e
.
toString
());
}
}
static
dynamic
_getDataSync
(
String
path
,
String
encoding
)
{
File
file
=
fs
.
file
(
path
);
return
encoding
==
null
?
file
.
readAsBytesSync
()
:
file
.
readAsStringSync
(
encoding:
_getEncodingByName
(
encoding
));
}
static
Encoding
_getEncodingByName
(
String
encoding
)
{
if
(
encoding
==
'system'
)
return
SYSTEM_ENCODING
;
else
if
(
encoding
!=
null
)
return
Encoding
.
getByName
(
encoding
);
return
null
;
}
Process
asProcess
(
bool
daemon
)
{
assert
(
stdout
is
List
<
int
>);
assert
(
stderr
is
List
<
int
>);
return
new
_ReplayProcess
(
this
,
daemon
);
}
}
/// A [Process] implementation derives its data from a recording fragment.
class
_ReplayProcess
implements
Process
{
@override
final
int
pid
;
final
List
<
int
>
_stdout
;
final
List
<
int
>
_stderr
;
final
StreamController
<
List
<
int
>>
_stdoutController
;
final
StreamController
<
List
<
int
>>
_stderrController
;
final
int
_exitCode
;
final
Completer
<
int
>
_exitCodeCompleter
;
_ReplayProcess
(
_ReplayProcessResult
result
,
bool
daemon
)
:
pid
=
result
.
pid
,
_stdout
=
result
.
stdout
,
_stderr
=
result
.
stderr
,
_stdoutController
=
new
StreamController
<
List
<
int
>>(),
_stderrController
=
new
StreamController
<
List
<
int
>>(),
_exitCode
=
result
.
exitCode
,
_exitCodeCompleter
=
new
Completer
<
int
>()
{
// Don't flush our stdio streams until we reach the outer event loop. This
// is necessary because some of our process invocations transform the stdio
// streams into broadcast streams (e.g. DeviceLogReader implementations),
// and delaying our stdio stream production until we reach the outer event
// loop allows all code running in the microtask loop to register as
// listeners on these streams before we flush them.
//
manager
=
await
ReplayProcessManager
.
create
(
dir
,
// TODO(tvolkert): Once https://github.com/flutter/flutter/issues/7166 is
// resolved, running on the outer event loop should be
// sufficient (as described above), and we should switch to
// Duration.ZERO. In the meantime, native file I/O
// operations are causing a Duration.ZERO callback here to
// run before our ProtocolDiscovery instantiation, and thus,
// we flush our stdio streams before our protocol discovery
// is listening on them (causing us to timeout waiting for
// the observatory port discovery).
new
Timer
(
const
Duration
(
milliseconds:
50
),
()
{
_stdoutController
.
add
(
_stdout
);
_stderrController
.
add
(
_stderr
);
if
(!
daemon
)
kill
();
});
// resolved, we can use the default `streamDelay`. In the
// meantime, native file I/O operations cause our `tail` process
// streams to flush before our protocol discovery is listening on
// them, causing us to timeout waiting for the observatory port.
streamDelay:
const
Duration
(
milliseconds:
50
),
);
}
on
ArgumentError
catch
(
error
)
{
throwToolExit
(
'Invalid replay-from:
$error
'
);
}
@override
Stream
<
List
<
int
>>
get
stdout
=>
_stdoutController
.
stream
;
@override
Stream
<
List
<
int
>>
get
stderr
=>
_stderrController
.
stream
;
@override
Future
<
int
>
get
exitCode
=>
_exitCodeCompleter
.
future
;
// TODO(tvolkert): Remove this once the dart sdk in both the target and
// the host have picked up dart-lang/sdk@e5a16b1
@override
// ignore: OVERRIDE_ON_NON_OVERRIDING_SETTER
set
exitCode
(
Future
<
int
>
exitCode
)
=>
throw
new
UnsupportedError
(
'set exitCode'
);
@override
IOSink
get
stdin
=>
throw
new
UnimplementedError
();
@override
bool
kill
([
ProcessSignal
signal
=
ProcessSignal
.
SIGTERM
])
{
if
(!
_exitCodeCompleter
.
isCompleted
)
{
_stdoutController
.
close
();
_stderrController
.
close
();
_exitCodeCompleter
.
complete
(
_exitCode
);
return
true
;
}
return
false
;
}
context
.
setVariable
(
ProcessManager
,
manager
);
}
packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
View file @
03d163ce
...
...
@@ -100,16 +100,15 @@ class FlutterCommandRunner extends CommandRunner<Null> {
hide:
!
verboseHelp
,
help:
'Enables recording of process invocations (including stdout and stderr of all such invocations),
\n
'
'and serializes that recording to the specified location. If the location is a directory, a ZIP
\n
'
'file named `recording.zip` will be created in that directory. Otherwise, a ZIP file will be
\n
'
'created with the path specified in this flag.'
);
'and serializes that recording to a directory with the path specified in this flag. If the
\n
'
'directory does not already exist, it will be created.'
);
argParser
.
addOption
(
'replay-from'
,
hide:
!
verboseHelp
,
help:
'Enables mocking of process invocations by replaying their stdout, stderr, and exit code from
\n
'
'the specified recording (obtained via --record-to).
If the location is a file, it is assumed to
\n
'
'
be a ZIP file structured according to the output of --record-to. If the location is a directory,
\n
'
'
it is assumed to be an unzipped version of such a ZIP file
.'
);
'the specified recording (obtained via --record-to).
The path specified in this flag must refer
\n
'
'
to a directory that holds serialized process invocations structured according to the output of
\n
'
'
--record-to
.'
);
}
@override
...
...
@@ -164,23 +163,11 @@ class FlutterCommandRunner extends CommandRunner<Null> {
throwToolExit
(
'--record-to and --replay-from cannot be used together.'
);
if
(
globalResults
[
'record-to'
]
!=
null
)
{
// Turn on recording.
String
recordTo
=
globalResults
[
'record-to'
].
trim
();
if
(
recordTo
.
isEmpty
)
recordTo
=
null
;
context
.
setVariable
(
ProcessManager
,
new
RecordingProcessManager
(
recordTo
));
enableRecordingProcessManager
(
globalResults
[
'record-to'
].
trim
());
}
if
(
globalResults
[
'replay-from'
]
!=
null
)
{
// Turn on replay-based mocking.
try
{
context
.
setVariable
(
ProcessManager
,
await
ReplayProcessManager
.
create
(
globalResults
[
'replay-from'
].
trim
(),
));
}
on
ArgumentError
{
throwToolExit
(
'--replay-from must specify a valid file or directory.'
);
}
await
enableReplayProcessManager
(
globalResults
[
'replay-from'
].
trim
());
}
logger
.
quiet
=
globalResults
[
'quiet'
];
...
...
packages/flutter_tools/pubspec.yaml
View file @
03d163ce
...
...
@@ -22,6 +22,7 @@ dependencies:
mustache
:
^0.2.5
package_config
:
'
>=0.1.5
<2.0.0'
path
:
^1.4.0
process
:
^1.0.0
pub_semver
:
^1.0.0
stack_trace
:
^1.4.0
usage
:
^2.2.1
...
...
packages/flutter_tools/test/all.dart
View file @
03d163ce
...
...
@@ -37,7 +37,6 @@ import 'install_test.dart' as install_test;
import
'logs_test.dart'
as
logs_test
;
import
'os_utils_test.dart'
as
os_utils_test
;
import
'packages_test.dart'
as
packages_test
;
import
'process_manager_test.dart'
as
process_manager_test
;
import
'protocol_discovery_test.dart'
as
protocol_discovery_test
;
import
'run_test.dart'
as
run_test
;
import
'stop_test.dart'
as
stop_test
;
...
...
@@ -77,7 +76,6 @@ void main() {
logs_test
.
main
();
os_utils_test
.
main
();
packages_test
.
main
();
process_manager_test
.
main
();
protocol_discovery_test
.
main
();
run_test
.
main
();
stop_test
.
main
();
...
...
packages/flutter_tools/test/data/process_manager/replay/001.sing.100.stderr
deleted
100644 → 0
View file @
24f1b2ee
Uh, pineapple pen
packages/flutter_tools/test/data/process_manager/replay/001.sing.100.stdout
deleted
100644 → 0
View file @
24f1b2ee
I have a pen
I have a pineapple
packages/flutter_tools/test/data/process_manager/replay/002.dance.101.stderr
deleted
100644 → 0
View file @
24f1b2ee
No one can dance like Psy
packages/flutter_tools/test/data/process_manager/replay/002.dance.101.stdout
deleted
100644 → 0
View file @
24f1b2ee
packages/flutter_tools/test/data/process_manager/replay/MANIFEST.txt
deleted
100644 → 0
View file @
24f1b2ee
[
{
"pid": 100,
"basename": "001.sing.100",
"executable": "sing",
"arguments": [
"ppap"
],
"mode": "ProcessStartMode.NORMAL",
"exitCode": 0
},
{
"pid": 101,
"basename": "002.dance.101",
"executable": "dance",
"arguments": [
"gangnam-style"
],
"stdoutEncoding": "system",
"stderrEncoding": "system",
"exitCode": 2
}
]
packages/flutter_tools/test/process_manager_test.dart
deleted
100644 → 0
View file @
24f1b2ee
// Copyright 2016 The Chromium 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
'dart:async'
;
import
'dart:convert'
;
import
'package:archive/archive.dart'
;
import
'package:flutter_tools/src/base/context.dart'
;
import
'package:flutter_tools/src/base/file_system.dart'
;
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/logger.dart'
;
import
'package:flutter_tools/src/base/os.dart'
;
import
'package:flutter_tools/src/base/process.dart'
;
import
'package:flutter_tools/src/base/process_manager.dart'
;
import
'package:path/path.dart'
as
path
;
import
'package:test/test.dart'
;
typedef
bool
Predicate
<
T
>(
T
item
);
/// Decodes a UTF8-encoded byte array into a list of Strings, where each list
/// entry represents a line of text.
List
<
String
>
_decode
(
List
<
int
>
data
)
=>
const
LineSplitter
().
convert
(
UTF8
.
decode
(
data
));
/// Consumes and returns an entire stream of bytes.
Future
<
List
<
int
>>
_consume
(
Stream
<
List
<
int
>>
stream
)
=>
stream
.
expand
((
List
<
int
>
data
)
=>
data
).
toList
();
void
main
(
)
{
group
(
'RecordingProcessManager'
,
()
{
Directory
tmp
;
ProcessManager
manager
;
setUp
(()
{
tmp
=
fs
.
systemTempDirectory
.
createTempSync
(
'flutter_tools_'
);
manager
=
new
RecordingProcessManager
(
tmp
.
path
);
});
tearDown
(()
{
tmp
.
deleteSync
(
recursive:
true
);
});
test
(
'start'
,
()
async
{
Process
process
=
await
manager
.
start
(
'echo'
,
<
String
>[
'foo'
]);
int
pid
=
process
.
pid
;
int
exitCode
=
await
process
.
exitCode
;
List
<
int
>
stdout
=
await
_consume
(
process
.
stdout
);
List
<
int
>
stderr
=
await
_consume
(
process
.
stderr
);
expect
(
exitCode
,
0
);
expect
(
_decode
(
stdout
),
<
String
>[
'foo'
]);
expect
(
stderr
,
isEmpty
);
// Force the recording to be written to disk.
await
runShutdownHooks
();
_Recording
recording
=
_Recording
.
readFrom
(
tmp
);
expect
(
recording
.
manifest
,
hasLength
(
1
));
Map
<
String
,
dynamic
>
entry
=
recording
.
manifest
.
first
;
expect
(
entry
[
'pid'
],
pid
);
expect
(
entry
[
'exitCode'
],
exitCode
);
expect
(
recording
.
stdoutForEntryAt
(
0
),
stdout
);
expect
(
recording
.
stderrForEntryAt
(
0
),
stderr
);
});
test
(
'run'
,
()
async
{
ProcessResult
result
=
await
manager
.
run
(
'echo'
,
<
String
>[
'bar'
]);
int
pid
=
result
.
pid
;
int
exitCode
=
result
.
exitCode
;
String
stdout
=
result
.
stdout
;
String
stderr
=
result
.
stderr
;
expect
(
exitCode
,
0
);
expect
(
stdout
,
'bar
\n
'
);
expect
(
stderr
,
isEmpty
);
// Force the recording to be written to disk.
await
runShutdownHooks
();
_Recording
recording
=
_Recording
.
readFrom
(
tmp
);
expect
(
recording
.
manifest
,
hasLength
(
1
));
Map
<
String
,
dynamic
>
entry
=
recording
.
manifest
.
first
;
expect
(
entry
[
'pid'
],
pid
);
expect
(
entry
[
'exitCode'
],
exitCode
);
expect
(
recording
.
stdoutForEntryAt
(
0
),
stdout
);
expect
(
recording
.
stderrForEntryAt
(
0
),
stderr
);
});
test
(
'runSync'
,
()
async
{
ProcessResult
result
=
manager
.
runSync
(
'echo'
,
<
String
>[
'baz'
]);
int
pid
=
result
.
pid
;
int
exitCode
=
result
.
exitCode
;
String
stdout
=
result
.
stdout
;
String
stderr
=
result
.
stderr
;
expect
(
exitCode
,
0
);
expect
(
stdout
,
'baz
\n
'
);
expect
(
stderr
,
isEmpty
);
// Force the recording to be written to disk.
await
runShutdownHooks
();
_Recording
recording
=
_Recording
.
readFrom
(
tmp
);
expect
(
recording
.
manifest
,
hasLength
(
1
));
Map
<
String
,
dynamic
>
entry
=
recording
.
manifest
.
first
;
expect
(
entry
[
'pid'
],
pid
);
expect
(
entry
[
'exitCode'
],
exitCode
);
expect
(
recording
.
stdoutForEntryAt
(
0
),
stdout
);
expect
(
recording
.
stderrForEntryAt
(
0
),
stderr
);
});
});
group
(
'ReplayProcessManager'
,
()
{
ProcessManager
manager
;
setUp
(()
async
{
await
runInMinimalContext
(()
async
{
Directory
dir
=
fs
.
directory
(
'test/data/process_manager/replay'
);
manager
=
await
ReplayProcessManager
.
create
(
dir
.
path
);
});
});
tearDown
(()
async
{
// Allow the replay manager to clean up
await
runShutdownHooks
();
});
test
(
'start'
,
()
async
{
Process
process
=
await
manager
.
start
(
'sing'
,
<
String
>[
'ppap'
]);
int
exitCode
=
await
process
.
exitCode
;
List
<
int
>
stdout
=
await
_consume
(
process
.
stdout
);
List
<
int
>
stderr
=
await
_consume
(
process
.
stderr
);
expect
(
process
.
pid
,
100
);
expect
(
exitCode
,
0
);
expect
(
_decode
(
stdout
),
<
String
>[
'I have a pen'
,
'I have a pineapple'
]);
expect
(
_decode
(
stderr
),
<
String
>[
'Uh, pineapple pen'
]);
});
test
(
'run'
,
()
async
{
ProcessResult
result
=
await
manager
.
run
(
'dance'
,
<
String
>[
'gangnam-style'
]);
expect
(
result
.
pid
,
101
);
expect
(
result
.
exitCode
,
2
);
expect
(
result
.
stdout
,
''
);
expect
(
result
.
stderr
,
'No one can dance like Psy
\n
'
);
});
test
(
'runSync'
,
()
{
ProcessResult
result
=
manager
.
runSync
(
'dance'
,
<
String
>[
'gangnam-style'
]);
expect
(
result
.
pid
,
101
);
expect
(
result
.
exitCode
,
2
);
expect
(
result
.
stdout
,
''
);
expect
(
result
.
stderr
,
'No one can dance like Psy
\n
'
);
});
});
}
Future
<
Null
>
runInMinimalContext
(
Future
<
dynamic
>
method
())
async
{
AppContext
context
=
new
AppContext
();
context
.
putIfAbsent
(
FileSystem
,
()
=>
new
LocalFileSystem
());
context
.
putIfAbsent
(
ProcessManager
,
()
=>
new
ProcessManager
());
context
.
putIfAbsent
(
Logger
,
()
=>
new
BufferLogger
());
context
.
putIfAbsent
(
OperatingSystemUtils
,
()
=>
new
OperatingSystemUtils
());
await
context
.
runInZone
(
method
);
}
/// A testing utility class that encapsulates a recording.
class
_Recording
{
final
File
file
;
final
Archive
_archive
;
_Recording
(
this
.
file
,
this
.
_archive
);
static
_Recording
readFrom
(
Directory
dir
)
{
File
file
=
fs
.
file
(
path
.
join
(
dir
.
path
,
RecordingProcessManager
.
kDefaultRecordTo
));
Archive
archive
=
new
ZipDecoder
().
decodeBytes
(
file
.
readAsBytesSync
());
return
new
_Recording
(
file
,
archive
);
}
List
<
Map
<
String
,
dynamic
>>
get
manifest
{
return
JSON
.
decoder
.
convert
(
_getFileContent
(
'MANIFEST.txt'
,
UTF8
));
}
dynamic
stdoutForEntryAt
(
int
index
)
=>
_getStdioContent
(
manifest
[
index
],
'stdout'
);
dynamic
stderrForEntryAt
(
int
index
)
=>
_getStdioContent
(
manifest
[
index
],
'stderr'
);
dynamic
_getFileContent
(
String
name
,
Encoding
encoding
)
{
List
<
int
>
bytes
=
_fileNamed
(
name
).
content
;
return
encoding
==
null
?
bytes
:
encoding
.
decode
(
bytes
);
}
dynamic
_getStdioContent
(
Map
<
String
,
dynamic
>
entry
,
String
type
)
{
String
basename
=
entry
[
'basename'
];
String
encodingName
=
entry
[
'
${type}
Encoding'
];
Encoding
encoding
;
if
(
encodingName
!=
null
)
encoding
=
encodingName
==
'system'
?
SYSTEM_ENCODING
:
Encoding
.
getByName
(
encodingName
);
return
_getFileContent
(
'
$basename
.
$type
'
,
encoding
);
}
ArchiveFile
_fileNamed
(
String
name
)
=>
_archive
.
firstWhere
(
_hasName
(
name
));
Predicate
<
ArchiveFile
>
_hasName
(
String
name
)
=>
(
ArchiveFile
file
)
=>
file
.
name
==
name
;
}
packages/flutter_tools/test/src/common.dart
View file @
03d163ce
...
...
@@ -3,9 +3,9 @@
// found in the LICENSE file.
import
'package:args/command_runner.dart'
;
import
'package:process/process.dart'
;
import
'package:flutter_tools/src/base/context.dart'
;
import
'package:flutter_tools/src/base/process_manager.dart'
;
import
'package:flutter_tools/src/runner/flutter_command.dart'
;
import
'package:flutter_tools/src/runner/flutter_command_runner.dart'
;
...
...
packages/flutter_tools/test/src/context.dart
View file @
03d163ce
...
...
@@ -10,7 +10,6 @@ import 'package:flutter_tools/src/base/file_system.dart';
import
'package:flutter_tools/src/base/io.dart'
;
import
'package:flutter_tools/src/base/logger.dart'
;
import
'package:flutter_tools/src/base/os.dart'
;
import
'package:flutter_tools/src/base/process_manager.dart'
;
import
'package:flutter_tools/src/cache.dart'
;
import
'package:flutter_tools/src/device.dart'
;
import
'package:flutter_tools/src/devfs.dart'
;
...
...
@@ -23,6 +22,7 @@ import 'package:flutter_tools/src/usage.dart';
import
'package:mockito/mockito.dart'
;
import
'package:path/path.dart'
as
path
;
import
'package:process/process.dart'
;
import
'package:test/test.dart'
;
/// Return the test logger. This assumes that the current Logger is a BufferLogger.
...
...
@@ -43,7 +43,7 @@ void testUsingContext(String description, dynamic testMethod(), {
// Initialize the test context with some default mocks.
// Seed these context entries first since others depend on them
testContext
.
putIfAbsent
(
FileSystem
,
()
=>
new
LocalFileSystem
());
testContext
.
putIfAbsent
(
ProcessManager
,
()
=>
new
ProcessManager
());
testContext
.
putIfAbsent
(
ProcessManager
,
()
=>
new
Local
ProcessManager
());
testContext
.
putIfAbsent
(
Logger
,
()
=>
new
BufferLogger
());
// Order-independent context entries
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment