Skip to content

Commit

Permalink
Implement RecordingFileSystem. (flutter#16)
Browse files Browse the repository at this point in the history
To be followed by ReplayFileSystem in an upcoming commit.

First part of flutter#11
  • Loading branch information
tvolkert authored Feb 1, 2017
1 parent eda5a8b commit 3ba4cf2
Show file tree
Hide file tree
Showing 21 changed files with 2,049 additions and 78 deletions.
54 changes: 46 additions & 8 deletions .analysis_options
Original file line number Diff line number Diff line change
@@ -1,30 +1,68 @@
analyzer:
strong-mode: true
language:
enableSuperMixins: true
enableStrictCallChecks: true
enableSuperMixins: true
errors:
# Allow having TODOs in the code
todo: ignore

linter:
rules:
# Errors
# these rules are documented on and in the same order as
# the Dart Lint rules page to make maintenance easier
# http://dart-lang.github.io/linter/lints/

# === error rules ===
- avoid_empty_else
# TODO - comment_references
- cancel_subscriptions
# TODO - close_sinks
- control_flow_in_finally
- empty_statements
- hash_and_equals
- invariant_booleans
- iterable_contains_unrelated_type
- list_remove_unrelated_type
- literal_only_boolean_expressions
- test_types_in_equals
- throw_in_finally
- unrelated_type_equality_checks
- valid_regexps

# Style
# TODO: - annotate_overrides
# === style rules ===
- always_declare_return_types
# TODO - always_specify_types
# TODO - annotate_overrides
# TODO - avoid_as
- avoid_init_to_null
- avoid_return_types_on_setters
- await_only_futures
- camel_case_types
# TODO: - comment_references
# TODO - camel_case_types
# TODO - constant_identifier_names
- control_flow_in_finally
- empty_catches
- empty_constructor_bodies
- hash_and_equals
- implementation_imports
- library_names
- library_prefixes
- non_constant_identifier_names
- one_member_abstracts
- only_throw_errors
- overridden_fields
- package_api_docs
- package_prefixed_library_names
- prefer_is_not_empty
# TODO - public_member_api_docs
- slash_for_doc_comments
- sort_constructors_first
# TODO - sort_unnamed_constructors_first
- super_goes_last
# TODO - type_annotate_public_apis
- type_init_formals
- unrelated_type_equality_checks
# TODO - unawaited_futures
- unnecessary_brace_in_string_interp
- unnecessary_getters_setters

# === pub rules ===
- package_names
7 changes: 5 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

* Improved `toString` implementations in file system entity classes
* Added `ForwardingFileSystem` and associated forwarding classes to the
main `file` library.
main `file` library
* Removed `FileSystem.pathSeparator`, and added a more comprehensive
`FileSystem.path` property.
`FileSystem.path` property
* Added `FileSystemEntity.basename` and `FileSystemEntity.dirname`
* Added the `record_replay` library
* Added the `testing` library

#### 1.0.1

Expand Down
9 changes: 9 additions & 0 deletions lib/record_replay.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// 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.

export 'src/backends/record_replay/events.dart'
show InvocationEvent, PropertyGetEvent, PropertySetEvent, MethodEvent;
export 'src/backends/record_replay/recording.dart';
export 'src/backends/record_replay/recording_file_system.dart'
show RecordingFileSystem;
23 changes: 23 additions & 0 deletions lib/src/backends/record_replay/common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// 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.

/// Encoded value of the file system in a recording.
const String kFileSystemEncodedValue = '__fs__';

/// The name of the recording manifest file.
const String kManifestName = 'MANIFEST.txt';

/// Gets an id guaranteed to be unique on this isolate for objects within this
/// library.
int newUid() => _nextUid++;
int _nextUid = 1;

/// Gets the name of the specified [symbol].
// TODO(tvolkert): Symbol.name (https://github.com/dart-lang/sdk/issues/28372)
String getSymbolName(Symbol symbol) {
// Format of `str` is `Symbol("<name>")`
String str = symbol.toString();
int offset = str.indexOf('"') + 1;
return str.substring(offset, str.indexOf('"', offset));
}
169 changes: 169 additions & 0 deletions lib/src/backends/record_replay/encoding.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// 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:convert';

import 'package:file/file.dart';
import 'package:path/path.dart' as p;

import 'common.dart';
import 'events.dart';
import 'recording_directory.dart';
import 'recording_file.dart';
import 'recording_file_system_entity.dart';
import 'recording_io_sink.dart';
import 'recording_link.dart';
import 'recording_random_access_file.dart';

/// Encodes an object into a JSON-ready representation.
typedef dynamic _Encoder(dynamic object);

/// This class is a work-around for the "is" operator not accepting a variable
/// value as its right operand (https://github.com/dart-lang/sdk/issues/27680).
class _TypeMatcher<T> {
/// Creates a type matcher for the given type parameter.
const _TypeMatcher();

/// Returns `true` if the given object is of type `T`.
bool check(dynamic object) => object is T;
}

/// Known encoders. Types not covered here will be encoded using
/// [_encodeDefault].
///
/// When encoding an object, we will walk this map in iteration order looking
/// for a matching encoder. Thus, when there are two encoders that match an
// object, the first one will win.
const Map<_TypeMatcher, _Encoder> _kEncoders = const <_TypeMatcher, _Encoder>{
const _TypeMatcher<num>(): _encodeRaw,
const _TypeMatcher<bool>(): _encodeRaw,
const _TypeMatcher<String>(): _encodeRaw,
const _TypeMatcher<Null>(): _encodeRaw,
const _TypeMatcher<List>(): _encodeRaw,
const _TypeMatcher<Map>(): _encodeMap,
const _TypeMatcher<Iterable>(): _encodeIterable,
const _TypeMatcher<Symbol>(): getSymbolName,
const _TypeMatcher<DateTime>(): _encodeDateTime,
const _TypeMatcher<Uri>(): _encodeUri,
const _TypeMatcher<p.Context>(): _encodePathContext,
const _TypeMatcher<EventImpl>(): _encodeEvent,
const _TypeMatcher<FileSystem>(): _encodeFileSystem,
const _TypeMatcher<RecordingDirectory>(): _encodeFileSystemEntity,
const _TypeMatcher<RecordingFile>(): _encodeFileSystemEntity,
const _TypeMatcher<RecordingLink>(): _encodeFileSystemEntity,
const _TypeMatcher<RecordingIOSink>(): _encodeIOSink,
const _TypeMatcher<RecordingRandomAccessFile>(): _encodeRandomAccessFile,
const _TypeMatcher<Encoding>(): _encodeEncoding,
const _TypeMatcher<FileMode>(): _encodeFileMode,
const _TypeMatcher<FileStat>(): _encodeFileStat,
const _TypeMatcher<FileSystemEntityType>(): _encodeFileSystemEntityType,
const _TypeMatcher<FileSystemEvent>(): _encodeFileSystemEvent,
};

/// Encodes [object] into a JSON-ready representation.
///
/// This function is intended to be used as the `toEncodable` argument to the
/// `JsonEncoder` constructors.
///
/// See also:
/// - [JsonEncoder.withIndent]
dynamic encode(dynamic object) {
_Encoder encoder = _encodeDefault;
for (_TypeMatcher matcher in _kEncoders.keys) {
if (matcher.check(object)) {
encoder = _kEncoders[matcher];
break;
}
}
return encoder(object);
}

/// Default encoder (used for types not covered in [_kEncoders]).
String _encodeDefault(dynamic object) => object.runtimeType.toString();

/// Pass-through encoder.
dynamic _encodeRaw(dynamic object) => object;

List<T> _encodeIterable<T>(Iterable<T> iterable) => iterable.toList();

/// Encodes the map keys, and passes the values through.
///
/// As [JsonEncoder] encodes an object graph, it will repeatedly call
/// `toEncodable` to encode unknown types, so any values in a map that need
/// special encoding will already be handled by `JsonEncoder`. However, the
/// encoder won't try to encode map *keys* by default, which is why we encode
/// them here.
Map<String, T> _encodeMap<T>(Map<dynamic, T> map) {
Map<String, T> encoded = <String, T>{};
for (dynamic key in map.keys) {
String encodedKey = encode(key);
encoded[encodedKey] = map[key];
}
return encoded;
}

int _encodeDateTime(DateTime dateTime) => dateTime.millisecondsSinceEpoch;

String _encodeUri(Uri uri) => uri.toString();

Map<String, String> _encodePathContext(p.Context context) => <String, String>{
'style': context.style.name,
'cwd': context.current,
};

Map<String, dynamic> _encodeEvent(EventImpl event) => event.encode();

String _encodeFileSystem(FileSystem fs) => kFileSystemEncodedValue;

/// Encodes a file system entity by using its `uid` as a reference identifier.
/// During replay, this allows us to tie the return value of of one event to
/// the object of another.
String _encodeFileSystemEntity(RecordingFileSystemEntity entity) {
return '${entity.runtimeType}@${entity.uid}';
}

String _encodeIOSink(RecordingIOSink sink) {
return '${sink.runtimeType}@${sink.uid}';
}

String _encodeRandomAccessFile(RecordingRandomAccessFile raf) {
return '${raf.runtimeType}@${raf.uid}';
}

String _encodeEncoding(Encoding encoding) => encoding.name;

String _encodeFileMode(FileMode fileMode) {
switch (fileMode) {
case FileMode.READ:
return 'READ';
case FileMode.WRITE:
return 'WRITE';
case FileMode.APPEND:
return 'APPEND';
case FileMode.WRITE_ONLY:
return 'WRITE_ONLY';
case FileMode.WRITE_ONLY_APPEND:
return 'WRITE_ONLY_APPEND';
}
throw new ArgumentError('Invalid value: $fileMode');
}

Map<String, dynamic> _encodeFileStat(FileStat stat) => <String, dynamic>{
'changed': stat.changed,
'modified': stat.modified,
'accessed': stat.accessed,
'type': stat.type,
'mode': stat.mode,
'size': stat.size,
'modeString': stat.modeString(),
};

String _encodeFileSystemEntityType(FileSystemEntityType type) =>
type.toString();

Map<String, dynamic> _encodeFileSystemEvent(FileSystemEvent event) =>
<String, dynamic>{
'type': event.type,
'path': event.path,
};
Loading

0 comments on commit 3ba4cf2

Please sign in to comment.