228 lines
5 KiB
Markdown
228 lines
5 KiB
Markdown
# Testing Utilities
|
|
|
|
The Process package provides comprehensive testing utilities for process-dependent code.
|
|
|
|
## Process Faking
|
|
|
|
### Basic Faking
|
|
|
|
```dart
|
|
final factory = Factory();
|
|
|
|
// Fake specific commands
|
|
factory.fake({
|
|
'ls': 'file1.txt\nfile2.txt',
|
|
'cat file1.txt': 'Hello, World!',
|
|
'grep pattern': (process) => 'Matched line',
|
|
});
|
|
|
|
// Run fake processes
|
|
final result = await factory.command('ls').run();
|
|
expect(result.output().trim(), equals('file1.txt\nfile2.txt'));
|
|
```
|
|
|
|
### Preventing Real Processes
|
|
|
|
```dart
|
|
// Prevent any real process execution
|
|
factory.fake().preventStrayProcesses();
|
|
|
|
// This will throw an exception
|
|
await factory.command('real-command').run();
|
|
```
|
|
|
|
### Dynamic Results
|
|
|
|
```dart
|
|
factory.fake({
|
|
'random': (process) =>
|
|
DateTime.now().millisecondsSinceEpoch.toString(),
|
|
'conditional': (process) =>
|
|
process.env['SUCCESS'] == 'true' ? 'success' : 'failure',
|
|
});
|
|
```
|
|
|
|
## Process Descriptions
|
|
|
|
### Basic Description
|
|
|
|
```dart
|
|
final description = FakeProcessDescription()
|
|
..withExitCode(0)
|
|
..replaceOutput('Test output')
|
|
..replaceErrorOutput('Test error');
|
|
|
|
factory.fake({
|
|
'test-command': description,
|
|
});
|
|
```
|
|
|
|
### Simulating Long-Running Processes
|
|
|
|
```dart
|
|
final description = FakeProcessDescription()
|
|
..withOutputSequence(['Step 1', 'Step 2', 'Step 3'])
|
|
..withDelay(Duration(milliseconds: 100))
|
|
..runsFor(duration: Duration(seconds: 1));
|
|
|
|
factory.fake({
|
|
'long-task': description,
|
|
});
|
|
```
|
|
|
|
### Simulating Process Failures
|
|
|
|
```dart
|
|
final description = FakeProcessDescription()
|
|
..withExitCode(1)
|
|
..replaceOutput('Operation failed')
|
|
..replaceErrorOutput('Error: Invalid input');
|
|
|
|
factory.fake({
|
|
'failing-task': description,
|
|
});
|
|
```
|
|
|
|
## Process Sequences
|
|
|
|
### Basic Sequences
|
|
|
|
```dart
|
|
final sequence = FakeProcessSequence()
|
|
..then(FakeProcessResult(output: 'First'))
|
|
..then(FakeProcessResult(output: 'Second'))
|
|
..then(FakeProcessResult(output: 'Third'));
|
|
|
|
factory.fake({
|
|
'sequential-task': sequence,
|
|
});
|
|
```
|
|
|
|
### Alternating Success/Failure
|
|
|
|
```dart
|
|
final sequence = FakeProcessSequence.alternating(3);
|
|
while (sequence.hasMore) {
|
|
final result = sequence.call() as FakeProcessResult;
|
|
print('Success: ${result.successful()}');
|
|
}
|
|
```
|
|
|
|
### Custom Sequences
|
|
|
|
```dart
|
|
final sequence = FakeProcessSequence.fromOutputs([
|
|
'Starting...',
|
|
'Processing...',
|
|
'Complete!',
|
|
]);
|
|
|
|
factory.fake({
|
|
'progress-task': sequence,
|
|
});
|
|
```
|
|
|
|
## Testing Process Pools
|
|
|
|
```dart
|
|
test('executes processes concurrently', () async {
|
|
factory.fake({
|
|
'task1': FakeProcessDescription()
|
|
..withDelay(Duration(seconds: 1))
|
|
..replaceOutput('Result 1'),
|
|
'task2': FakeProcessDescription()
|
|
..withDelay(Duration(seconds: 1))
|
|
..replaceOutput('Result 2'),
|
|
});
|
|
|
|
final results = await factory.pool((pool) {
|
|
pool.command('task1');
|
|
pool.command('task2');
|
|
}).start();
|
|
|
|
expect(results.successful(), isTrue);
|
|
expect(results.total, equals(2));
|
|
});
|
|
```
|
|
|
|
## Testing Process Pipes
|
|
|
|
```dart
|
|
test('pipes output between processes', () async {
|
|
factory.fake({
|
|
'generate': 'initial data',
|
|
'transform': (process) => process.input.toUpperCase(),
|
|
'filter': (process) => process.input.contains('DATA') ? process.input : '',
|
|
});
|
|
|
|
final result = await factory.pipeThrough((pipe) {
|
|
pipe.command('generate');
|
|
pipe.command('transform');
|
|
pipe.command('filter');
|
|
}).run();
|
|
|
|
expect(result.output(), equals('INITIAL DATA'));
|
|
});
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. Use `preventStrayProcesses()` in tests to catch unintended process execution
|
|
2. Simulate realistic scenarios with delays and sequences
|
|
3. Test both success and failure cases
|
|
4. Test process configuration (environment, working directory, etc.)
|
|
5. Test process coordination (pools and pipes)
|
|
6. Use process descriptions for complex behaviors
|
|
7. Test timeout and error handling
|
|
8. Mock system-specific behaviors
|
|
9. Clean up resources in tests
|
|
10. Test real-time output handling
|
|
|
|
## Example Test Suite
|
|
|
|
```dart
|
|
void main() {
|
|
group('Process Manager', () {
|
|
late Factory factory;
|
|
|
|
setUp(() {
|
|
factory = Factory();
|
|
factory.fake().preventStrayProcesses();
|
|
});
|
|
|
|
test('handles successful process', () async {
|
|
factory.fake({
|
|
'successful-task': FakeProcessDescription()
|
|
..withExitCode(0)
|
|
..replaceOutput('Success!'),
|
|
});
|
|
|
|
final result = await factory
|
|
.command('successful-task')
|
|
.run();
|
|
|
|
expect(result.successful(), isTrue);
|
|
expect(result.output(), equals('Success!'));
|
|
});
|
|
|
|
test('handles process failure', () async {
|
|
factory.fake({
|
|
'failing-task': FakeProcessDescription()
|
|
..withExitCode(1)
|
|
..replaceErrorOutput('Failed!'),
|
|
});
|
|
|
|
final result = await factory
|
|
.command('failing-task')
|
|
.run();
|
|
|
|
expect(result.failed(), isTrue);
|
|
expect(result.errorOutput(), equals('Failed!'));
|
|
});
|
|
});
|
|
}
|
|
```
|
|
|
|
For more information, see:
|
|
- [Process Execution](execution.md) for basic process management
|
|
- [Process Coordination](coordination.md) for pools and pipes
|