blob: 3288b700ee8e4a74e4c74cca4f28551d190c0b62 [file] [log] [blame]
// 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 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file/record_replay.dart';
import 'package:file/testing.dart';
import 'package:file/src/backends/record_replay/mutable_recording.dart';
import 'package:file/src/backends/record_replay/recording_proxy_mixin.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'common_tests.dart';
void main() {
group('SupportingClasses', () {
BasicClass delegate;
RecordingClass rc;
MutableRecording recording;
setUp(() {
delegate = new BasicClass();
rc = new RecordingClass(
delegate: delegate,
stopwatch: new FakeStopwatch(10),
destination: new MemoryFileSystem().directory('/tmp')..createSync(),
);
recording = rc.recording;
});
group('InvocationEvent', () {
test('recordsAllPropertyGetMetadata', () {
delegate.basicProperty = 'foo';
String value = rc.basicProperty;
expect(recording.events, hasLength(1));
expect(
recording.events[0],
getsProperty('basicProperty')
.on(rc)
.withResult(value)
.withTimestamp(10));
});
test('recordsAllPropertySetMetadata', () {
rc.basicProperty = 'foo';
expect(recording.events, hasLength(1));
expect(
recording.events[0],
setsProperty('basicProperty')
.on(rc)
.toValue('foo')
.withTimestamp(10));
});
test('recordsAllMethodInvocationMetadata', () {
String result = rc.basicMethod('foo', namedArg: 'bar');
expect(recording.events, hasLength(1));
expect(
recording.events[0],
invokesMethod('basicMethod')
.on(rc)
.withPositionalArguments(<String>['foo'])
.withNamedArgument('namedArg', 'bar')
.withResult(result)
.withTimestamp(10));
});
test('resultIncompleteUntilFutureCompletes', () async {
delegate.basicProperty = 'foo';
rc.futureProperty; // ignore: unawaited_futures
expect(recording.events, hasLength(1));
expect(
recording.events[0],
getsProperty('futureProperty')
.on(rc)
.withResult(isNull)
.withTimestamp(10));
await recording.events[0].done;
expect(
recording.events[0],
getsProperty('futureProperty')
.on(rc)
.withResult('future.foo')
.withTimestamp(10));
});
test('resultIncompleteUntilStreamCompletes', () async {
Stream<String> stream = rc.streamMethod('foo', namedArg: 'bar');
stream.listen((_) {});
expect(recording.events, hasLength(1));
expect(
recording.events[0],
invokesMethod('streamMethod')
.on(rc)
.withPositionalArguments(<String>['foo'])
.withNamedArgument('namedArg', 'bar')
.withResult(allOf(isList, isEmpty))
.withTimestamp(10));
await recording.events[0].done;
expect(
recording.events[0],
invokesMethod('streamMethod')
.on(rc)
.withPositionalArguments(<String>['foo'])
.withNamedArgument('namedArg', 'bar')
.withResult(<String>['stream', 'foo', 'bar'])
.withTimestamp(10));
});
});
group('MutableRecording', () {
group('flush', () {
List<Map<String, dynamic>> loadManifestFromFileSystem() {
List<FileSystemEntity> files = recording.destination.listSync();
expect(files.length, 1);
expect(files[0], isFile);
expect(files[0].basename, 'MANIFEST.txt');
File manifestFile = files[0];
return new JsonDecoder().convert(manifestFile.readAsStringSync());
}
test('writesManifestToFileSystemAsJson', () async {
rc.basicProperty = 'foo';
String value = rc.basicProperty;
rc.basicMethod(value, namedArg: 'bar');
await recording.flush();
List<Map<String, dynamic>> manifest = loadManifestFromFileSystem();
expect(manifest, hasLength(3));
expect(manifest[0], <String, dynamic>{
'type': 'set',
'property': 'basicProperty=',
'value': 'foo',
'object': 'RecordingClass',
'result': null,
'timestamp': 10,
});
expect(manifest[1], <String, dynamic>{
'type': 'get',
'property': 'basicProperty',
'object': 'RecordingClass',
'result': 'foo',
'timestamp': 11,
});
expect(manifest[2], <String, dynamic>{
'type': 'invoke',
'method': 'basicMethod',
'positionalArguments': <String>['foo'],
'namedArguments': <String, dynamic>{'namedArg': 'bar'},
'object': 'RecordingClass',
'result': 'foo.bar',
'timestamp': 12
});
});
test('doesntAwaitPendingResultsUnlessToldToDoSo', () async {
rc.futureMethod('foo', namedArg: 'bar'); // ignore: unawaited_futures
await recording.flush();
List<Map<String, dynamic>> manifest = loadManifestFromFileSystem();
expect(manifest[0], containsPair('result', null));
});
test('succeedsIfAwaitPendingResultsThatComplete', () async {
rc.futureMethod('foo', namedArg: 'bar'); // ignore: unawaited_futures
await recording.flush(
awaitPendingResults: const Duration(seconds: 30));
List<Map<String, dynamic>> manifest = loadManifestFromFileSystem();
expect(manifest[0], containsPair('result', 'future.foo.bar'));
});
test('succeedsIfAwaitPendingResultsThatTimeout', () async {
rc.veryLongFutureMethod(); // ignore: unawaited_futures
DateTime before = new DateTime.now();
await recording.flush(
awaitPendingResults: const Duration(milliseconds: 250));
DateTime after = new DateTime.now();
Duration delta = after.difference(before);
List<Map<String, dynamic>> manifest = loadManifestFromFileSystem();
expect(manifest[0], containsPair('result', isNull));
expect(delta.inMilliseconds, greaterThanOrEqualTo(250));
});
test('throwsIfAlreadyFlushing', () {
rc.basicProperty = 'foo';
recording.flush();
expect(recording.flush(), throwsA(isStateError));
});
});
});
});
group('RecordingFileSystem', () {
RecordingFileSystem fs;
MemoryFileSystem delegate;
LiveRecording recording;
setUp(() {
delegate = new MemoryFileSystem();
fs = new RecordingFileSystem(
delegate: delegate,
destination: new MemoryFileSystem().directory('/tmp')..createSync(),
);
recording = fs.recording;
});
runCommonTests(
() => fs,
skip: <String>[
'File > open', // Not yet implemented in MemoryFileSystem
],
);
group('recording', () {
test('supportsMultipleActions', () {
fs.directory('/foo').createSync();
fs.file('/foo/bar').writeAsStringSync('BAR');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(4));
expect(events[0], invokesMethod('directory'));
expect(events[1], invokesMethod('createSync'));
expect(events[2], invokesMethod('file'));
expect(events[3], invokesMethod('writeAsStringSync'));
expect(events[0].result, events[1].object);
expect(events[2].result, events[3].object);
});
group('FileSystem', () {
test('directory', () {
fs.directory('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('directory').on(fs).withPositionalArguments(
<String>['/foo']).withResult(isDirectory),
);
});
test('file', () {
fs.file('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('file')
.on(fs)
.withPositionalArguments(<String>['/foo']).withResult(isFile),
);
});
test('link', () {
fs.link('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('link')
.on(fs)
.withPositionalArguments(<String>['/foo']).withResult(isLink),
);
});
test('path', () {
fs.path;
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
getsProperty('path')
.on(fs)
.withResult(const isInstanceOf<p.Context>()),
);
});
test('systemTempDirectory', () {
fs.systemTempDirectory;
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
getsProperty('systemTempDirectory')
.on(fs)
.withResult(isDirectory));
});
group('currentDirectory', () {
test('get', () {
fs.currentDirectory;
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
getsProperty('currentDirectory')
.on(fs)
.withResult(isDirectory));
});
test('setToString', () {
delegate.directory('/foo').createSync();
fs.currentDirectory = '/foo';
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(events[0],
setsProperty('currentDirectory').on(fs).toValue('/foo'));
});
test('setToRecordingDirectory', () {
delegate.directory('/foo').createSync();
fs.currentDirectory = fs.directory('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events.length, greaterThanOrEqualTo(2));
expect(events[0], invokesMethod().withResult(isDirectory));
Directory directory = events[0].result;
expect(
events,
contains(setsProperty('currentDirectory')
.on(fs)
.toValue(directory)));
});
test('setToNonRecordingDirectory', () {
Directory dir = delegate.directory('/foo');
dir.createSync();
fs.currentDirectory = dir;
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(events[0],
setsProperty('currentDirectory').on(fs).toValue(isDirectory));
});
});
test('stat', () async {
delegate.file('/foo').createSync();
await fs.stat('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('stat').on(fs).withPositionalArguments(
<String>['/foo']).withResult(isFileStat),
);
});
test('statSync', () {
delegate.file('/foo').createSync();
fs.statSync('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('statSync').on(fs).withPositionalArguments(
<String>['/foo']).withResult(isFileStat),
);
});
test('identical', () async {
delegate.file('/foo').createSync();
delegate.file('/bar').createSync();
await fs.identical('/foo', '/bar');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('identical').on(fs).withPositionalArguments(
<String>['/foo', '/bar']).withResult(isFalse));
});
test('identicalSync', () {
delegate.file('/foo').createSync();
delegate.file('/bar').createSync();
fs.identicalSync('/foo', '/bar');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('identicalSync').on(fs).withPositionalArguments(
<String>['/foo', '/bar']).withResult(isFalse));
});
test('isWatchSupported', () {
fs.isWatchSupported;
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(events[0],
getsProperty('isWatchSupported').on(fs).withResult(isFalse));
});
test('type', () async {
delegate.file('/foo').createSync();
await fs.type('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('type').on(fs).withPositionalArguments(
<String>['/foo']).withResult(FileSystemEntityType.FILE));
});
test('typeSync', () {
delegate.file('/foo').createSync();
fs.typeSync('/foo');
List<InvocationEvent<dynamic>> events = recording.events;
expect(events, hasLength(1));
expect(
events[0],
invokesMethod('typeSync').on(fs).withPositionalArguments(
<String>['/foo']).withResult(FileSystemEntityType.FILE));
});
});
group('Directory', () {
test('create', () async {
await fs.directory('/foo').create();
expect(
recording.events,
contains(invokesMethod('create')
.on(isDirectory)
.withNoNamedArguments()
.withResult(isDirectory)));
});
test('createSync', () {
fs.directory('/foo').createSync();
expect(
recording.events,
contains(invokesMethod('createSync')
.on(isDirectory)
.withNoNamedArguments()
.withResult(isNull)));
});
test('list', () async {
await delegate.directory('/foo').create();
await delegate.directory('/bar').create();
await delegate.file('/baz').create();
Stream<FileSystemEntity> stream = fs.directory('/').list();
await stream.drain();
expect(
recording.events,
contains(invokesMethod('list')
.on(isDirectory)
.withNoNamedArguments()
.withResult(hasLength(3))),
);
});
});
group('File', () {});
group('Link', () {});
});
});
}
// ignore: public_member_api_docs
class BasicClass {
// ignore: public_member_api_docs
String basicProperty;
// ignore: public_member_api_docs
Future<String> get futureProperty async => 'future.$basicProperty';
// ignore: public_member_api_docs
String basicMethod(String positionalArg, {String namedArg}) =>
'$positionalArg.$namedArg';
// ignore: public_member_api_docs
Future<String> futureMethod(String positionalArg, {String namedArg}) async {
await new Future<Null>.delayed(const Duration(milliseconds: 500));
String basicValue = basicMethod(positionalArg, namedArg: namedArg);
return 'future.$basicValue';
}
// ignore: public_member_api_docs
Stream<String> streamMethod(String positionalArg, {String namedArg}) async* {
yield 'stream';
yield positionalArg;
yield namedArg;
}
// ignore: public_member_api_docs
Future<String> veryLongFutureMethod() async {
await new Future<Null>.delayed(const Duration(seconds: 1));
return 'future';
}
// ignore: public_member_api_docs
Stream<String> infiniteStreamMethod() async* {
yield 'stream';
int i = 0;
while (i >= 0) {
yield '${i++}';
await new Future<Null>.delayed(const Duration(seconds: 1));
}
}
}
// ignore: public_member_api_docs
class RecordingClass extends Object
with RecordingProxyMixin
implements BasicClass {
// ignore: public_member_api_docs
final BasicClass delegate;
// ignore: public_member_api_docs
RecordingClass({
this.delegate,
this.stopwatch,
Directory destination,
})
: recording = new MutableRecording(destination) {
methods.addAll(<Symbol, Function>{
#basicMethod: delegate.basicMethod,
#futureMethod: delegate.futureMethod,
#streamMethod: delegate.streamMethod,
#veryLongFutureMethod: delegate.veryLongFutureMethod,
#infiniteStreamMethod: delegate.infiniteStreamMethod,
});
properties.addAll(<Symbol, Function>{
#basicProperty: () => delegate.basicProperty,
const Symbol('basicProperty='): (String value) {
delegate.basicProperty = value;
},
#futureProperty: () => delegate.futureProperty,
});
}
@override
final MutableRecording recording;
@override
final Stopwatch stopwatch;
}
// ignore: public_member_api_docs
class FakeStopwatch implements Stopwatch {
int _value;
// ignore: public_member_api_docs
FakeStopwatch(this._value);
@override
int get elapsedMilliseconds => _value++;
@override
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}