Skip to content

Commit 3934d0b

Browse files
authored
Add the contents of the package. (dart-archive/test_descriptor#1)
This is ported from the scheduled_test package, altered to remove scheduling and to use a more modern style.
1 parent 4029d52 commit 3934d0b

15 files changed

+1329
-0
lines changed

pkgs/test_descriptor/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,53 @@
11
The `test_descriptor` package provides a convenient, easy-to-read API for
22
defining and verifying directory structures in tests.
3+
4+
We recommend that you import this library with the `d` prefix. The
5+
[`d.dir()`][dir] and [`d.file()`][file] functions are the main entrypoints. They
6+
define a filesystem structure that can be created using
7+
[`Descriptor.create()`][create] and verified using
8+
[`Descriptor.validate()`][validate]. For example:
9+
10+
[dir]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/dir.html
11+
[file]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/file.html
12+
[create]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/Descriptor/create.html
13+
[validate]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/Descriptor/validate.html
14+
15+
```dart
16+
import 'dart:io';
17+
18+
import 'package:test_descriptor/test_descriptor.dart' as d;
19+
20+
void main() {
21+
test("Directory.rename", () async {
22+
await d.dir("parent", [
23+
d.file("sibling", "sibling-contents"),
24+
d.dir("old-name", [
25+
d.file("child", "child-contents")
26+
])
27+
]).create();
28+
29+
await new Directory("${d.sandbox}/parent/old-name")
30+
.rename("${d.sandbox}/parent/new-name");
31+
32+
await d.dir("parent", [
33+
d.file("sibling", "sibling-contents"),
34+
d.dir("new-name", [
35+
d.file("child", "child-contents")
36+
])
37+
]).validate();
38+
});
39+
}
40+
```
41+
42+
By default, descriptors create entries in a temporary sandbox directory,
43+
[`d.sandbox`][sandbox]. A new sandbox is automatically created the first time
44+
you create a descriptor in a given test, and automatically deleted once the test
45+
finishes running.
46+
47+
[sandbox]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/sandbox.html
48+
49+
This package is [`term_glyph`][term_glyph] aware. It will decide whether to use
50+
ASCII or Unicode glyphs based on the [`glyph.ascii`][glyph.ascii] attribute.
51+
52+
[term_glyph]: https://pub.dartlang.org/packages/term_glyph
53+
[gylph.ascii]: https://www.dartdocs.org/documentation/term_glyph/latest/term_glyph/ascii.html
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
/// A declarative description of a filesystem entry.
8+
///
9+
/// This may be extended outside this package.
10+
abstract class Descriptor {
11+
/// This entry's basename.
12+
final String name;
13+
14+
Descriptor(this.name);
15+
16+
/// Creates this entry within the [parent] directory, which defaults to
17+
/// [sandbox].
18+
Future create([String parent]);
19+
20+
/// Validates that the physical file system under [parent] (which defaults to
21+
/// [sandbox]) contains an entry that matches this descriptor.
22+
Future validate([String parent]);
23+
24+
/// Returns a human-friendly tree-style description of this descriptor.
25+
String describe();
26+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:async';
6+
import 'dart:io';
7+
8+
import 'package:async/async.dart';
9+
import 'package:path/path.dart' as p;
10+
import 'package:term_glyph/term_glyph.dart' as glyph;
11+
import 'package:test/test.dart';
12+
13+
import 'descriptor.dart';
14+
import 'file_descriptor.dart';
15+
import 'sandbox.dart';
16+
import 'utils.dart';
17+
18+
/// A descriptor describing a directory that may contain nested descriptors.
19+
///
20+
/// In addition to the normal descriptor methods, this has a [load] method that
21+
/// allows it to be used as a virtual filesystem.
22+
///
23+
/// This may be extended outside this package.
24+
class DirectoryDescriptor extends Descriptor {
25+
/// Descriptors for entries in this directory.
26+
///
27+
/// This may be modified.
28+
final List<Descriptor> contents;
29+
30+
DirectoryDescriptor(String name, Iterable<Descriptor> contents)
31+
: contents = contents.toList(),
32+
super(name);
33+
34+
/// Creates a directory descriptor named [name] that describes the physical
35+
/// directory at [path].
36+
factory DirectoryDescriptor.fromFilesystem(String name, String path) {
37+
return new DirectoryDescriptor(name,
38+
new Directory(path).listSync().map((entity) {
39+
// Ignore hidden files.
40+
if (p.basename(entity.path).startsWith(".")) return null;
41+
42+
if (entity is Directory) {
43+
return new DirectoryDescriptor.fromFilesystem(
44+
p.basename(entity.path), entity.path);
45+
} else if (entity is File) {
46+
return new FileDescriptor(
47+
p.basename(entity.path), entity.readAsBytesSync());
48+
}
49+
// Ignore broken symlinks.
50+
}).where((path) => path != null));
51+
}
52+
53+
Future create([String parent]) async {
54+
var fullPath = p.join(parent ?? sandbox, name);
55+
await new Directory(fullPath).create(recursive: true);
56+
await Future.wait(contents.map((entry) => entry.create(fullPath)));
57+
}
58+
59+
Future validate([String parent]) async {
60+
var fullPath = p.join(parent ?? sandbox, name);
61+
if (!(await new Directory(fullPath).exists())) {
62+
fail('Directory not found: "${prettyPath(fullPath)}".');
63+
}
64+
65+
await waitAndReportErrors(
66+
contents.map((entry) => entry.validate(fullPath)));
67+
}
68+
69+
/// Treats this descriptor as a virtual filesystem and loads the binary
70+
/// contents of the [FileDescriptor] at the given relative [url], which may be
71+
/// a [Uri] or a [String].
72+
///
73+
/// The [parent] parameter should only be passed by subclasses of
74+
/// [DirectoryDescriptor] that are recursively calling [load]. It's the
75+
/// URL-format path of the directories that have been loaded so far.
76+
Stream<List<int>> load(url, [String parents]) {
77+
String path;
78+
if (url is String) {
79+
path = url;
80+
} else if (url is Uri) {
81+
path = url.toString();
82+
} else {
83+
throw new ArgumentError.value(url, "url", "must be a Uri or a String.");
84+
}
85+
86+
if (!p.url.isWithin('.', path)) {
87+
throw new ArgumentError.value(
88+
url, "url", "must be relative and beneath the base URL.");
89+
}
90+
91+
return StreamCompleter.fromFuture(new Future.sync(() {
92+
var split = p.url.split(p.url.normalize(path));
93+
var file = split.length == 1;
94+
var matchingEntries = contents.where((entry) {
95+
return entry.name == split.first &&
96+
file
97+
? entry is FileDescriptor
98+
: entry is DirectoryDescriptor;
99+
}).toList();
100+
101+
var type = file ? 'file' : 'directory';
102+
var parentsAndSelf = parents == null ? name : p.url.join(parents, name);
103+
if (matchingEntries.isEmpty) {
104+
fail('Couldn\'t find a $type descriptor named "${split.first}" within '
105+
'"$parentsAndSelf".');
106+
} else if (matchingEntries.length > 1) {
107+
fail('Found multiple $type descriptors named "${split.first}" within '
108+
'"$parentsAndSelf".');
109+
} else {
110+
var remainingPath = split.sublist(1);
111+
if (remainingPath.isEmpty) {
112+
return (matchingEntries.first as FileDescriptor).readAsBytes();
113+
} else {
114+
return (matchingEntries.first as DirectoryDescriptor)
115+
.load(p.url.joinAll(remainingPath), parentsAndSelf);
116+
}
117+
}
118+
}));
119+
}
120+
121+
String describe() {
122+
if (contents.isEmpty) return name;
123+
124+
var buffer = new StringBuffer();
125+
buffer.writeln(name);
126+
for (var entry in contents.take(contents.length - 1)) {
127+
var entryString = prefixLines(
128+
entry.describe(), '${glyph.verticalLine} ',
129+
first: '${glyph.teeRight}${glyph.horizontalLine}'
130+
'${glyph.horizontalLine} ');
131+
buffer.writeln(entryString);
132+
}
133+
134+
var lastEntryString = prefixLines(contents.last.describe(), ' ',
135+
first: '${glyph.bottomLeftCorner}${glyph.horizontalLine}'
136+
'${glyph.horizontalLine} ');
137+
buffer.write(lastEntryString);
138+
return buffer.toString();
139+
}
140+
}

0 commit comments

Comments
 (0)