Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
D
dart.googlesource.com-test_process
Manage
Activity
Members
Code
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Model registry
Analyze
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
fuchsia-mirror
dart.googlesource.com-test_process
Commits
aac945c3
Commit
aac945c3
authored
8 years ago
by
Natalie Weizenbaum
Committed by
GitHub
8 years ago
Browse files
Options
Downloads
Patches
Plain Diff
Add the package implementation. (#1)
parent
0eaf342a
No related branches found
No related tags found
No related merge requests found
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
lib/test_process.dart
+224
-0
224 additions, 0 deletions
lib/test_process.dart
pubspec.yaml
+7
-1
7 additions, 1 deletion
pubspec.yaml
test/test_process_test.dart
+131
-0
131 additions, 0 deletions
test/test_process_test.dart
with
362 additions
and
1 deletion
lib/test_process.dart
0 → 100644
+
224
−
0
View file @
aac945c3
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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
'dart:io'
;
import
'package:async/async.dart'
;
import
'package:meta/meta.dart'
;
import
'package:path/path.dart'
as
p
;
import
'package:test/test.dart'
;
/// A wrapper for [Process] that provides a convenient API for testing its
/// standard IO and interacting with it from a test.
///
/// If the test fails, this will automatically print out any stdout and stderr
/// from the process to aid debugging.
///
/// This may be extended to provide custom implementations of [stdoutStream] and
/// [stderrStream]. These will automatically be picked up by the [stdout] and
/// [stderr] queues, but the debug log will still contain the original output.
class
TestProcess
{
/// The underlying process.
final
Process
_process
;
/// A human-friendly description of this process.
final
String
description
;
/// A [StreamQueue] that emits each line of stdout from the process.
///
/// A copy of the underlying stream can be retreived using [stdoutStream].
StreamQueue
<
String
>
get
stdout
=
>
_stdout
;
StreamQueue
<
String
>
_stdout
;
/// A [StreamQueue] that emits each line of stderr from the process.
///
/// A copy of the underlying stream can be retreived using [stderrStream].
StreamQueue
<
String
>
get
stderr
=
>
_stderr
;
StreamQueue
<
String
>
_stderr
;
/// A splitter that can emit new copies of [stdout].
final
StreamSplitter
<
String
>
_stdoutSplitter
;
/// A splitter that can emit new copies of [stderr].
final
StreamSplitter
<
String
>
_stderrSplitter
;
/// The standard input sink for this process.
IOSink
get
stdin
=
>
_process
.
stdin
;
/// A buffer of mixed stdout and stderr lines.
final
_log
=
<
String
>[];
/// Whether [_log] has been passed to [printOnFailure] yet.
bool
_loggedOutput
=
false
;
/// Completes to [_process]'s exit code if it's exited, otherwise completes to
/// `null` immediately.
Future
<
int
>
get
_exitCodeOrNull
async
=
>
await
_process
.
exitCode
.
timeout
(
Duration
.
ZERO
,
onTimeout:
()
=
>
null
);
/// Starts a process.
///
/// [executable], [arguments], [workingDirectory], and [environment] have the
/// same meaning as for [Process.start].
///
/// [description] is a string description of this process; it defaults to the
/// command-line invocation. [encoding] is the [Encoding] that will be used
/// for the process's input and output; it defaults to [UTF8].
///
/// If [forwardStdio] is `true`, the process's stdout and stderr will be
/// printed to the console as they appear. This is only intended to be set
/// temporarily to help when debugging test failures.
static
Future
<
TestProcess
>
start
(
String
executable
,
Iterable
<
String
>
arguments
,
{
String
workingDirectory
,
Map
<
String
,
String
>
environment
,
bool
includeParentEnvironment:
true
,
bool
runInShell:
false
,
String
description
,
Encoding
encoding
,
bool
forwardStdio:
false
})
async
{
var
process
=
await
Process
.
start
(
executable
,
arguments
.
toList
(),
workingDirectory:
workingDirectory
,
environment:
environment
,
includeParentEnvironment:
includeParentEnvironment
,
runInShell:
runInShell
);
if
(
description
==
null
)
{
var
humanExecutable
=
p
.
isWithin
(
p
.
current
,
executable
)
?
p
.
relative
(
executable
)
:
executable
;
description
=
"
$humanExecutable
${arguments.join(" ")}
"
;
}
encoding
??=
UTF8
;
return
new
TestProcess
(
process
,
description
,
encoding:
encoding
,
forwardStdio:
forwardStdio
);
}
/// Creates a [TestProcess] for [process].
///
/// The [description], [encoding], and [forwardStdio] are the same as those to
/// [start].
///
/// This is protected, which means it should only be called by subclasses.
@protected
TestProcess
(
Process
process
,
this
.
description
,
{
Encoding
encoding
,
bool
forwardStdio:
false
})
:
_process
=
process
,
_stdoutSplitter
=
new
StreamSplitter
(
process
.
stdout
.
transform
(
encoding
.
decoder
)
.
transform
(
const
LineSplitter
())),
_stderrSplitter
=
new
StreamSplitter
(
process
.
stderr
.
transform
(
encoding
.
decoder
)
.
transform
(
const
LineSplitter
()))
{
addTearDown
(
_tearDown
);
expect
(
_process
.
exitCode
.
then
((
_
)
=
>
_logOutput
()),
completes
,
reason:
"Process `
$description
` never exited."
);
_stdout
=
new
StreamQueue
(
stdoutStream
());
_stderr
=
new
StreamQueue
(
stderrStream
());
// Listen eagerly so that the lines are interleaved properly between the two
// streams.
stdoutStream
()
.
listen
((
line
)
{
if
(
forwardStdio
)
print
(
line
);
_log
.
add
(
"
$line
"
);
});
stderrStream
()
.
listen
((
line
)
{
if
(
forwardStdio
)
print
(
line
);
_log
.
add
(
"[e]
$line
"
);
});
}
/// A callback that's run when the test completes.
Future
_tearDown
()
async
{
// If the process is already dead, do nothing.
if
(
await
_exitCodeOrNull
!=
null
)
return
;
_process
.
kill
(
ProcessSignal
.
SIGKILL
);
// Log output now rather than waiting for the exitCode callback so that
// it's visible even if we time out waiting for the process to die.
await
_logOutput
();
}
/// Formats the contents of [_log] and passes them to [printOnFailure].
Future
_logOutput
()
async
{
if
(
_loggedOutput
)
return
;
_loggedOutput
=
true
;
var
exitCode
=
await
_exitCodeOrNull
;
// Wait a timer tick to ensure that all available lines have been flushed to
// [_log].
await
new
Future
.
delayed
(
Duration
.
ZERO
);
var
buffer
=
new
StringBuffer
();
buffer
.
write
(
"Process `
$description
` "
);
if
((
await
_exitCodeOrNull
)
==
null
)
{
buffer
.
writeln
(
"was killed with SIGKILL in a tear-down. Output:"
);
}
else
{
buffer
.
writeln
(
"exited with exitCode
$exitCode
. Output:"
);
}
buffer
.
writeln
(
_log
.
join
(
"
\n
"
));
printOnFailure
(
buffer
.
toString
());
}
/// Returns a copy of [stdout] as a single-subscriber stream.
///
/// Each time this is called, it will return a separate copy that will start
/// from the beginning of the process.
///
/// This can be overridden by subclasses to return a derived standard output
/// stream. This stream will then be used for [stdout].
Stream
<
String
>
stdoutStream
()
=
>
_stdoutSplitter
.
split
();
/// Returns a copy of [stderr] as a single-subscriber stream.
///
/// Each time this is called, it will return a separate copy that will start
/// from the beginning of the process.
///
/// This can be overridden by subclasses to return a derived standard output
/// stream. This stream will then be used for [stderr].
Stream
<
String
>
stderrStream
()
=
>
_stderrSplitter
.
split
();
/// Sends [signal] to the process.
///
/// This is meant for sending specific signals. If you just want to kill the
/// process, use [kill] instead.
///
/// Throws an [UnsupportedError] on Windows.
void
signal
(
ProcessSignal
signal
)
{
if
(
Platform
.
isWindows
)
{
throw
new
UnsupportedError
(
"TestProcess.signal() isn't supported on Windows."
);
}
_process
.
kill
(
signal
);
}
/// Kills the process (with SIGKILL on POSIX operating systems), and returns a
/// future that completes once it's dead.
///
/// If this is called after the process is already dead, it does nothing.
Future
kill
()
async
{
_process
.
kill
(
ProcessSignal
.
SIGKILL
);
await
_process
.
exitCode
;
}
/// Waits for the process to exit, and verifies that the exit code matches
/// [expectedExitCode] (if given).
///
/// If this is called after the process is already dead, it verifies its
/// existing exit code.
Future
shouldExit
([
expectedExitCode
])
async
{
var
exitCode
=
await
_process
.
exitCode
;
if
(
expectedExitCode
==
null
)
return
;
expect
(
exitCode
,
expectedExitCode
,
reason:
"Process `
$description
` had an unexpected exit code."
);
}
}
This diff is collapsed.
Click to expand it.
pubspec.yaml
+
7
−
1
View file @
aac945c3
name
:
test_process
version
:
1.0.0-
dev
version
:
1.0.0-
rc.1
description
:
A library for testing subprocesses.
author
:
Dart Team <misc@dartlang.org>
homepage
:
https://github.com/dart-lang/test_process
...
...
@@ -8,4 +8,10 @@ environment:
sdk
:
'
>=1.8.0
<2.0.0'
dependencies
:
async
:
"
^1.12.0"
meta
:
"
>=0.9.0
<2.0.0"
path
:
"
^1.0.0"
test
:
"
^0.12.19"
dev_dependencies
:
test_descriptor
:
"
^1.0.0"
This diff is collapsed.
Click to expand it.
test/test_process_test.dart
0 → 100644
+
131
−
0
View file @
aac945c3
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. 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:io'
;
import
'package:path/path.dart'
as
p
;
import
'package:test/test.dart'
;
import
'package:test_descriptor/test_descriptor.dart'
as
d
;
import
'package:test_process/test_process.dart'
;
final
throwsTestFailure
=
throwsA
(
new
isInstanceOf
<
TestFailure
>());
void
main
()
{
group
(
"shouldExit()"
,
()
{
test
(
"succeeds when the process exits with the given exit code"
,
()
async
{
var
process
=
await
startDartProcess
(
'exitCode = 42;'
);
await
process
.
shouldExit
(
greaterThan
(
12
));
});
test
(
"fails when the process exits with a different exit code"
,
()
async
{
var
process
=
await
startDartProcess
(
'exitCode = 1;'
);
expect
(
process
.
shouldExit
(
greaterThan
(
12
)),
throwsTestFailure
);
});
test
(
"allows any exit code without an assertion"
,
()
async
{
var
process
=
await
startDartProcess
(
'exitCode = 1;'
);
await
process
.
shouldExit
();
});
});
test
(
"kill() stops the process"
,
()
async
{
var
process
=
await
startDartProcess
(
'while (true);'
);
// Should terminate.
await
process
.
kill
();
});
group
(
"stdout and stderr"
,
()
{
test
(
"expose the process's standard io"
,
()
async
{
var
process
=
await
startDartProcess
(
r''
'
print("hello");
stderr.writeln("hi");
print("
\n
world");
'''
);
expect
(
process
.
stdout
,
emitsInOrder
([
'hello'
,
''
,
'world'
,
emitsDone
]));
expect
(
process
.
stderr
,
emitsInOrder
([
'hi'
,
emitsDone
]));
await
process
.
shouldExit
(
0
);
});
test
(
"close when the process exits"
,
()
async
{
var
process
=
await
startDartProcess
(
''
);
expect
(
expectLater
(
process
.
stdout
,
emits
(
'hello'
)),
throwsTestFailure
);
expect
(
expectLater
(
process
.
stderr
,
emits
(
'world'
)),
throwsTestFailure
);
await
process
.
shouldExit
(
0
);
});
});
test
(
"stdoutStream() and stderrStream() copy the process's standard io"
,
()
async
{
var
process
=
await
startDartProcess
(
r''
'
print("hello");
stderr.writeln("hi");
print("
\n
world");
'''
);
expect
(
process
.
stdoutStream
(),
emitsInOrder
([
'hello'
,
''
,
'world'
,
emitsDone
]));
expect
(
process
.
stdoutStream
(),
emitsInOrder
([
'hello'
,
''
,
'world'
,
emitsDone
]));
expect
(
process
.
stderrStream
(),
emitsInOrder
([
'hi'
,
emitsDone
]));
expect
(
process
.
stderrStream
(),
emitsInOrder
([
'hi'
,
emitsDone
]));
await
process
.
shouldExit
(
0
);
expect
(
process
.
stdoutStream
(),
emitsInOrder
([
'hello'
,
''
,
'world'
,
emitsDone
]));
expect
(
process
.
stderrStream
(),
emitsInOrder
([
'hi'
,
emitsDone
]));
});
test
(
"stdin writes to the process"
,
()
async
{
var
process
=
await
startDartProcess
(
r''
'
stdinLines.listen((line) => print(">
$line
"));
'''
);
process
.
stdin
.
writeln
(
"hello"
);
await
expectLater
(
process
.
stdout
,
emits
(
"> hello"
));
process
.
stdin
.
writeln
(
"world"
);
await
expectLater
(
process
.
stdout
,
emits
(
"> world"
));
await
process
.
kill
();
});
test
(
"signal sends a signal to the process"
,
()
async
{
var
process
=
await
startDartProcess
(
r''
'
ProcessSignal.SIGHUP.watch().listen((_) => print("HUP"));
print("ready");
'''
);
await
expectLater
(
process
.
stdout
,
emits
(
'ready'
));
process
.
signal
(
ProcessSignal
.
SIGHUP
);
await
expectLater
(
process
.
stdout
,
emits
(
'HUP'
));
process
.
kill
();
},
testOn:
"!windows"
);
}
/// Starts a Dart process running [script] in a main method.
Future
<
TestProcess
>
startDartProcess
(
String
script
)
{
var
dartPath
=
p
.
join
(
d
.
sandbox
,
'test.dart'
);
new
File
(
dartPath
)
.
writeAsStringSync
(
'''
import '
dart:
async
';
import '
dart:
convert
';
import '
dart:
io
';
var stdinLines = stdin
.transform(UTF8.decoder)
.transform(new LineSplitter());
void main() {
$script
}
'''
);
return
TestProcess
.
start
(
Platform
.
executable
,
[
'--checked'
,
dartPath
]);
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment