Skip to content

Commit

Permalink
Merge pull request flutter#6 from dart-lang/fix-tests
Browse files Browse the repository at this point in the history
Fix case-insensitive listing.
  • Loading branch information
nex3 authored Dec 7, 2016
2 parents 5f50153 + 3dc117d commit a4c87fc
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 42 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 1.1.4

* Throw an exception when listing globs whose initial paths don't exist in
case-insensitive mode. This matches the case-sensitive behavior.

## 1.1.3

* Support `string_scanner` 1.0.0.
Expand Down
124 changes: 90 additions & 34 deletions lib/src/list_tree.dart
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,6 @@ class _ListTreeNode {
/// its children.
bool get _isIntermediate {
if (_validator != null) return false;
if (!_caseSensitive) return false;
return children.keys.every((sequence) =>
sequence.nodes.length == 1 && sequence.nodes.first is LiteralNode);
}
Expand Down Expand Up @@ -318,11 +317,10 @@ class _ListTreeNode {
.where((entity) => _matches(p.relative(entity.path, from: dir)));
}

var resultGroup = new StreamGroup<FileSystemEntity>();

// Don't spawn extra [Directory.list] calls when we already know exactly
// which subdirectories we're interested in.
if (_isIntermediate) {
if (_isIntermediate && _caseSensitive) {
var resultGroup = new StreamGroup<FileSystemEntity>();
children.forEach((sequence, child) {
resultGroup.add(child.list(
p.join(dir, (sequence.nodes.single as LiteralNode).text),
Expand All @@ -332,35 +330,65 @@ class _ListTreeNode {
return resultGroup.stream;
}

var resultController = new StreamController<FileSystemEntity>(sync: true);
resultGroup.add(resultController.stream);
new Directory(dir).list(followLinks: followLinks).listen((entity) {
var basename = p.relative(entity.path, from: dir);
if (_matches(basename)) resultController.add(entity);

children.forEach((sequence, child) {
if (entity is! Directory) return;
if (!sequence.matches(basename)) return;
var stream = child.list(p.join(dir, basename), followLinks: followLinks)
.handleError((_) {}, test: (error) {
// Ignore errors from directories not existing. We do this here so
// that we only ignore warnings below wild cards. For example, the
// glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
// succeed if "foo/bar/qux/baz" doesn't exist.
return error is FileSystemException &&
(error.osError.errorCode == _ENOENT ||
error.osError.errorCode == _ENOENT_WIN);
});
resultGroup.add(stream);
});
},
onError: resultController.addError,
onDone: () {
resultController.close();
resultGroup.close();
return StreamCompleter.fromFuture(() async {
var entities = await new Directory(dir)
.list(followLinks: followLinks).toList();
await _validateIntermediateChildrenAsync(dir, entities);

var resultGroup = new StreamGroup<FileSystemEntity>();
var resultController = new StreamController<FileSystemEntity>(sync: true);
resultGroup.add(resultController.stream);
for (var entity in entities) {
var basename = p.relative(entity.path, from: dir);
if (_matches(basename)) resultController.add(entity);

children.forEach((sequence, child) {
if (entity is! Directory) return;
if (!sequence.matches(basename)) return;
var stream = child
.list(p.join(dir, basename), followLinks: followLinks)
.handleError((_) {}, test: (error) {
// Ignore errors from directories not existing. We do this here so
// that we only ignore warnings below wild cards. For example, the
// glob "foo/bar/*/baz" should fail if "foo/bar" doesn't exist but
// succeed if "foo/bar/qux/baz" doesn't exist.
return error is FileSystemException &&
(error.osError.errorCode == _ENOENT ||
error.osError.errorCode == _ENOENT_WIN);
});
resultGroup.add(stream);
});
}
resultController.close();
resultGroup.close();
return resultGroup.stream;
}());
}

return resultGroup.stream;
/// If this is a case-insensitive list, validates that all intermediate
/// children (according to [_isIntermediate]) match at least one entity in
/// [entities].
///
/// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
/// "foo/bar" doesn't exist.
Future _validateIntermediateChildrenAsync(String dir,
List<FileSystemEntity> entities) async {
if (_caseSensitive) return;

for (var sequence in children.keys) {
var child = children[sequence];
if (!child._isIntermediate) continue;
if (entities.any((entity) =>
sequence.matches(p.relative(entity.path, from: dir)))) {
continue;
}

// We know this will fail, we're just doing it to force dart:io to emit
// the exception it would if we were listing case-sensitively.
await child
.list(p.join(dir, (sequence.nodes.single as LiteralNode).text))
.toList();
}
}

/// Synchronously lists all entities within [dir] matching this node or its
Expand All @@ -377,16 +405,18 @@ class _ListTreeNode {

// Don't spawn extra [Directory.listSync] calls when we already know exactly
// which subdirectories we're interested in.
if (_isIntermediate) {
if (_isIntermediate && _caseSensitive) {
return children.keys.expand((sequence) {
return children[sequence].listSync(
p.join(dir, (sequence.nodes.single as LiteralNode).text),
followLinks: followLinks);
});
}

return new Directory(dir).listSync(followLinks: followLinks)
.expand((entity) {
var entities = new Directory(dir).listSync(followLinks: followLinks);
_validateIntermediateChildrenSync(dir, entities);

return entities.expand((entity) {
var entities = <FileSystemEntity>[];
var basename = p.relative(entity.path, from: dir);
if (_matches(basename)) entities.add(entity);
Expand Down Expand Up @@ -416,6 +446,32 @@ class _ListTreeNode {
});
}

/// If this is a case-insensitive list, validates that all intermediate
/// children (according to [_isIntermediate]) match at least one entity in
/// [entities].
///
/// This ensures that listing "foo/bar/*" fails on case-sensitive systems if
/// "foo/bar" doesn't exist.
void _validateIntermediateChildrenSync(String dir,
List<FileSystemEntity> entities) {
if (_caseSensitive) return;

children.forEach((sequence, child) {
if (!child._isIntermediate) return;
if (entities.any((entity) =>
sequence.matches(p.relative(entity.path, from: dir)))) {
return;
}

// If there are no [entities] that match [sequence], manually list the
// directory to force `dart:io` to throw an error. This allows us to
// ensure that listing "foo/bar/*" fails on case-sensitive systems if
// "foo/bar" doesn't exist.
child
.listSync(p.join(dir, (sequence.nodes.single as LiteralNode).text));
});
}

/// Returns whether the native [path] matches [_validator].
bool _matches(String path) {
if (_validator == null) return false;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: glob
version: 1.1.3
version: 1.1.4
author: "Dart Team <[email protected]>"
homepage: https://github.com/dart-lang/glob
description: Bash-style filename globbing.
Expand Down
30 changes: 23 additions & 7 deletions test/list_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ void main() {
expect(new Glob("*", context: p.url).list, throwsStateError);
});

test("reports exceptions for non-existent directories", () {
test("reports exceptions for non-existent case-sensitive directories", () {
schedule(() {
expect(new Glob("non/existent/**").list().toList(),
expect(new Glob("non/existent/**", caseSensitive: true).list().toList(),
throwsA(new isInstanceOf<FileSystemException>()));
});
});

test("reports exceptions for non-existent case-insensitive directories",
() {
schedule(() {
expect(new Glob("non/existent/**", caseSensitive: false).list().toList(),
throwsA(new isInstanceOf<FileSystemException>()));
});
});
Expand All @@ -44,9 +52,17 @@ void main() {
expect(new Glob("*", context: p.url).listSync, throwsStateError);
});

test("reports exceptions for non-existent directories", () {
test("reports exceptions for non-existent case-sensitive directories", () {
schedule(() {
expect(new Glob("non/existent/**", caseSensitive: true).listSync,
throwsA(new isInstanceOf<FileSystemException>()));
});
});

test("reports exceptions for non-existent case-insensitive directories",
() {
schedule(() {
expect(new Glob("non/existent/**").listSync,
expect(new Glob("non/existent/**", caseSensitive: false).listSync,
throwsA(new isInstanceOf<FileSystemException>()));
});
});
Expand Down Expand Up @@ -286,7 +302,7 @@ void main() {
p.join("foo", "baz", "bang"),
p.join("foo", "baz", "qux")
])));
});
}, skip: "Broken by sdk#28015.");

test("lists a subdirectory that sometimes exists", () {
d.dir("top", [
Expand Down Expand Up @@ -316,8 +332,8 @@ void main() {
});

test("options preserve case-insensitivity", () {
expect(list("foo/{bar,baz}/qux", caseSensitive: false),
completion(equals([p.join("foo", "baz", "qux")])));
// expect(list("foo/{bar,baz}/qux", caseSensitive: false),
// completion(equals([p.join("foo", "baz", "qux")])));
expect(list("foo/{BAR,BAZ}/qux", caseSensitive: false),
completion(equals([p.join("foo", "baz", "qux")])));
});
Expand Down

0 comments on commit a4c87fc

Please sign in to comment.