Skip to content

Commit e30f99c

Browse files
authored
Serialize results of certain file invocations to separate file. (flutter#19)
This uses the work that was done in flutter#18 to wire up special handling of certain file operations such that their results will be written out to dedicated recording files. (more work towards flutter#11)
1 parent 42bf782 commit e30f99c

File tree

6 files changed

+557
-51
lines changed

6 files changed

+557
-51
lines changed

lib/src/backends/record_replay/mutable_recording.dart

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:async';
66
import 'dart:convert';
77

88
import 'package:file/file.dart';
9+
import 'package:intl/intl.dart';
910

1011
import 'common.dart';
1112
import 'encoding.dart';
@@ -41,7 +42,7 @@ class MutableRecording implements LiveRecording {
4142
Iterable<Future<Null>> futures =
4243
_events.map((LiveInvocationEvent<dynamic> event) => event.done);
4344
await Future
44-
.wait<String>(futures)
45+
.wait<Null>(futures)
4546
.timeout(awaitPendingResults, onTimeout: () {});
4647
}
4748
Directory dir = destination;
@@ -53,6 +54,20 @@ class MutableRecording implements LiveRecording {
5354
}
5455
}
5556

57+
/// Returns a new file for use with this recording.
58+
///
59+
/// The file name will combine the specified [name] with [newUid] to ensure
60+
/// that its name is unique among all recording files.
61+
///
62+
/// It is up to the caller to create the file - it will not exist in the
63+
/// file system when it is returned from this method.
64+
File newFile(String name) {
65+
String basename = '${new NumberFormat('000').format(newUid())}.$name';
66+
String dirname = destination.path;
67+
String path = destination.fileSystem.path.join(dirname, basename);
68+
return destination.fileSystem.file(path);
69+
}
70+
5671
/// Adds the specified [event] to this recording.
5772
void add(LiveInvocationEvent<dynamic> event) => _events.add(event);
5873
}

lib/src/backends/record_replay/recording_file.dart

Lines changed: 209 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@ import 'dart:convert';
88
import 'package:file/file.dart';
99
import 'package:file/src/io.dart' as io;
1010

11+
import 'mutable_recording.dart';
1112
import 'recording_file_system.dart';
1213
import 'recording_file_system_entity.dart';
1314
import 'recording_io_sink.dart';
1415
import 'recording_random_access_file.dart';
16+
import 'result_reference.dart';
1517

1618
/// [File] implementation that records all invocation activity to its file
1719
/// system's recording.
@@ -31,14 +33,14 @@ class RecordingFile extends RecordingFileSystemEntity<File, io.File>
3133
#lastModifiedSync: delegate.lastModifiedSync,
3234
#open: _open,
3335
#openSync: _openSync,
34-
#openRead: delegate.openRead,
36+
#openRead: _openRead,
3537
#openWrite: _openWrite,
36-
#readAsBytes: delegate.readAsBytes,
37-
#readAsBytesSync: delegate.readAsBytesSync,
38-
#readAsString: delegate.readAsString,
39-
#readAsStringSync: delegate.readAsStringSync,
40-
#readAsLines: delegate.readAsLines,
41-
#readAsLinesSync: delegate.readAsLinesSync,
38+
#readAsBytes: _readAsBytes,
39+
#readAsBytesSync: _readAsBytesSync,
40+
#readAsString: _readAsString,
41+
#readAsStringSync: _readAsStringSync,
42+
#readAsLines: _readAsLines,
43+
#readAsLinesSync: _readAsLinesSync,
4244
#writeAsBytes: _writeAsBytes,
4345
#writeAsBytesSync: delegate.writeAsBytesSync,
4446
#writeAsString: _writeAsString,
@@ -65,13 +67,60 @@ class RecordingFile extends RecordingFileSystemEntity<File, io.File>
6567
RandomAccessFile _openSync({FileMode mode: FileMode.READ}) =>
6668
_wrapRandomAccessFile(delegate.openSync(mode: mode));
6769

70+
StreamReference<List<int>> _openRead([int start, int end]) =>
71+
new _ByteArrayStreamReference(
72+
recording,
73+
'openRead',
74+
delegate.openRead(start, end),
75+
);
76+
6877
IOSink _openWrite({FileMode mode: FileMode.WRITE, Encoding encoding: UTF8}) {
6978
return new RecordingIOSink(
7079
fileSystem,
7180
delegate.openWrite(mode: mode, encoding: encoding),
7281
);
7382
}
7483

84+
FutureReference<List<int>> _readAsBytes() => new _ByteArrayFutureReference(
85+
recording,
86+
'readAsBytes',
87+
delegate.readAsBytes(),
88+
);
89+
90+
ResultReference<List<int>> _readAsBytesSync() => new _ByteArrayReference(
91+
recording,
92+
'readAsBytesSync',
93+
delegate.readAsBytesSync(),
94+
);
95+
96+
FutureReference<String> _readAsString({Encoding encoding: UTF8}) =>
97+
new _FileContentFutureReference(
98+
recording,
99+
'readAsString',
100+
delegate.readAsString(encoding: encoding),
101+
);
102+
103+
ResultReference<String> _readAsStringSync({Encoding encoding: UTF8}) =>
104+
new _FileContentReference(
105+
recording,
106+
'readAsStringSync',
107+
delegate.readAsStringSync(encoding: encoding),
108+
);
109+
110+
FutureReference<List<String>> _readAsLines({Encoding encoding: UTF8}) =>
111+
new _LinesFutureReference(
112+
recording,
113+
'readAsLines',
114+
delegate.readAsLines(encoding: encoding),
115+
);
116+
117+
ResultReference<List<String>> _readAsLinesSync({Encoding encoding: UTF8}) =>
118+
new _LinesReference(
119+
recording,
120+
'readAsLinesSync',
121+
delegate.readAsLinesSync(encoding: encoding),
122+
);
123+
75124
Future<File> _writeAsBytes(
76125
List<int> bytes, {
77126
FileMode mode: FileMode.WRITE,
@@ -89,3 +138,156 @@ class RecordingFile extends RecordingFileSystemEntity<File, io.File>
89138
.writeAsString(contents, mode: mode, encoding: encoding, flush: flush)
90139
.then(wrap);
91140
}
141+
142+
class _ByteArrayStreamReference extends StreamReference<List<int>> {
143+
final File file;
144+
IOSink _sink;
145+
146+
_ByteArrayStreamReference(
147+
MutableRecording recording, String name, Stream<List<int>> stream)
148+
: file = recording.newFile(name)..createSync(),
149+
super(stream);
150+
151+
@override
152+
void onData(List<int> event) {
153+
if (_sink == null) {
154+
_sink = file.openWrite();
155+
}
156+
_sink.add(event);
157+
}
158+
159+
@override
160+
void onDone() {
161+
if (_sink != null) {
162+
_sink.close();
163+
}
164+
}
165+
166+
@override
167+
dynamic get serializedValue => '!${file.basename}';
168+
169+
// TODO(tvolkert): remove `.then()` once Dart 1.22 is in stable
170+
@override
171+
Future<Null> get complete =>
172+
Future.wait(<Future<dynamic>>[super.complete, _sink.done]).then((_) {});
173+
}
174+
175+
class _ByteArrayFutureReference extends FutureReference<List<int>> {
176+
final File file;
177+
178+
_ByteArrayFutureReference(
179+
MutableRecording recording, String name, Future<List<int>> future)
180+
: file = recording.newFile(name),
181+
super(future);
182+
183+
@override
184+
Future<List<int>> get value {
185+
return super.value.then((List<int> bytes) async {
186+
await file.writeAsBytes(bytes, flush: true);
187+
return bytes;
188+
});
189+
}
190+
191+
@override
192+
dynamic get serializedValue => '!${file.basename}';
193+
}
194+
195+
class _ByteArrayReference extends ResultReference<List<int>> {
196+
final File file;
197+
final List<int> bytes;
198+
199+
_ByteArrayReference(MutableRecording recording, String name, this.bytes)
200+
: file = recording.newFile(name);
201+
202+
@override
203+
List<int> get value {
204+
file.writeAsBytesSync(bytes, flush: true);
205+
return bytes;
206+
}
207+
208+
@override
209+
List<int> get recordedValue => bytes;
210+
211+
@override
212+
dynamic get serializedValue => '!${file.basename}';
213+
}
214+
215+
class _FileContentFutureReference extends FutureReference<String> {
216+
final File file;
217+
218+
_FileContentFutureReference(
219+
MutableRecording recording, String name, Future<String> future)
220+
: file = recording.newFile(name),
221+
super(future);
222+
223+
@override
224+
Future<String> get value {
225+
return super.value.then((String content) async {
226+
await file.writeAsString(content, flush: true);
227+
return content;
228+
});
229+
}
230+
231+
@override
232+
dynamic get serializedValue => '!${file.basename}';
233+
}
234+
235+
class _FileContentReference extends ResultReference<String> {
236+
final File file;
237+
final String content;
238+
239+
_FileContentReference(MutableRecording recording, String name, this.content)
240+
: file = recording.newFile(name);
241+
242+
@override
243+
String get value {
244+
file.writeAsStringSync(content, flush: true);
245+
return content;
246+
}
247+
248+
@override
249+
String get recordedValue => content;
250+
251+
@override
252+
dynamic get serializedValue => '!${file.basename}';
253+
}
254+
255+
class _LinesFutureReference extends FutureReference<List<String>> {
256+
final File file;
257+
258+
_LinesFutureReference(
259+
MutableRecording recording, String name, Future<List<String>> future)
260+
: file = recording.newFile(name),
261+
super(future);
262+
263+
@override
264+
Future<List<String>> get value {
265+
return super.value.then((List<String> lines) async {
266+
await file.writeAsString(lines.join('\n'), flush: true);
267+
return lines;
268+
});
269+
}
270+
271+
@override
272+
dynamic get serializedValue => '!${file.basename}';
273+
}
274+
275+
class _LinesReference extends ResultReference<List<String>> {
276+
final File file;
277+
final List<String> lines;
278+
279+
_LinesReference(MutableRecording recording, String name, this.lines)
280+
: file = recording.newFile(name);
281+
282+
@override
283+
List<String> get value {
284+
file.writeAsStringSync(lines.join('\n'), flush: true);
285+
return lines;
286+
}
287+
288+
@override
289+
List<String> get recordedValue => lines;
290+
291+
@override
292+
dynamic get serializedValue => '!${file.basename}';
293+
}

lib/src/backends/record_replay/result_reference.dart

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
import 'dart:async';
66

7+
import 'package:meta/meta.dart';
8+
79
import 'encoding.dart';
810
import 'events.dart';
911
import 'recording_proxy_mixin.dart';
@@ -87,8 +89,9 @@ class FutureReference<T> extends ResultReference<Future<T>> {
8789
@override
8890
T get recordedValue => _value;
8991

92+
// TODO(tvolkert): remove `.then()` once Dart 1.22 is in stable
9093
@override
91-
Future<Null> get complete => value;
94+
Future<Null> get complete => value.then<Null>((_) {});
9295
}
9396

9497
/// Wraps a stream result.
@@ -128,19 +131,35 @@ class StreamReference<T> extends ResultReference<Stream<T>> {
128131
return _stream.listen(
129132
(T element) {
130133
_data.add(element);
134+
onData(element);
131135
_controller.add(element);
132136
},
133137
onError: (dynamic error, StackTrace stackTrace) {
134138
// TODO(tvolkert): Record errors
135139
_controller.addError(error, stackTrace);
136140
},
137141
onDone: () {
142+
onDone();
138143
_completer.complete();
139144
_controller.close();
140145
},
141146
);
142147
}
143148

149+
/// Called when an event is received from the underlying delegate stream.
150+
///
151+
/// Subclasses may override this method to be notified when events are
152+
/// fired from the underlying stream.
153+
@protected
154+
void onData(T event) {}
155+
156+
/// Called when the underlying delegate stream fires a "done" event.
157+
///
158+
/// Subclasses may override this method to be notified when the underlying
159+
/// stream is done.
160+
@protected
161+
void onDone() {}
162+
144163
@override
145164
Stream<T> get value => _controller.stream;
146165

lib/src/testing/core_matchers.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ void expectFileSystemException(dynamic message, void callback()) {
5757
expect(callback, throwsFileSystemException(message));
5858
}
5959

60+
/// Matcher that successfully matches against a [FileSystemEntity] that
61+
/// exists ([FileSystemEntity.existsSync] returns true).
62+
const Matcher exists = const _Exists();
63+
6064
class _FileSystemException extends Matcher {
6165
final Matcher _matcher;
6266

@@ -109,3 +113,25 @@ class _HasPath extends Matcher {
109113
return desc;
110114
}
111115
}
116+
117+
class _Exists extends Matcher {
118+
const _Exists();
119+
120+
@override
121+
bool matches(dynamic item, Map<dynamic, dynamic> matchState) =>
122+
item is FileSystemEntity && item.existsSync();
123+
124+
@override
125+
Description describe(Description description) =>
126+
description.add('a file system entity that exists');
127+
128+
@override
129+
Description describeMismatch(
130+
dynamic item,
131+
Description description,
132+
Map<dynamic, dynamic> matchState,
133+
bool verbose,
134+
) {
135+
return description.add('does not exist');
136+
}
137+
}

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ description: A pluggable, mockable file system abstraction for Dart.
88
homepage: https://github.com/matanlurey/file
99

1010
dependencies:
11+
intl: ^0.14.0
1112
meta: ^1.0.4
1213
path: ^1.4.0
1314

0 commit comments

Comments
 (0)