Skip to content
This repository was archived by the owner on Feb 10, 2025. It is now read-only.

Add the contents of the package. #1

Merged
merged 1 commit into from
Jan 18, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,53 @@
The `test_descriptor` package provides a convenient, easy-to-read API for
defining and verifying directory structures in tests.

We recommend that you import this library with the `d` prefix. The
[`d.dir()`][dir] and [`d.file()`][file] functions are the main entrypoints. They
define a filesystem structure that can be created using
[`Descriptor.create()`][create] and verified using
[`Descriptor.validate()`][validate]. For example:

[dir]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/dir.html
[file]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/file.html
[create]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/Descriptor/create.html
[validate]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/Descriptor/validate.html

```dart
import 'dart:io';

import 'package:test_descriptor/test_descriptor.dart' as d;

void main() {
test("Directory.rename", () async {
await d.dir("parent", [
d.file("sibling", "sibling-contents"),
d.dir("old-name", [
d.file("child", "child-contents")
])
]).create();

await new Directory("${d.sandbox}/parent/old-name")
.rename("${d.sandbox}/parent/new-name");

await d.dir("parent", [
d.file("sibling", "sibling-contents"),
d.dir("new-name", [
d.file("child", "child-contents")
])
]).validate();
});
}
```

By default, descriptors create entries in a temporary sandbox directory,
[`d.sandbox`][sandbox]. A new sandbox is automatically created the first time
you create a descriptor in a given test, and automatically deleted once the test
finishes running.

[sandbox]: https://www.dartdocs.org/documentation/test_descriptor/latest/test_descriptor/sandbox.html

This package is [`term_glyph`][term_glyph] aware. It will decide whether to use
ASCII or Unicode glyphs based on the [`glyph.ascii`][glyph.ascii] attribute.

[term_glyph]: https://pub.dartlang.org/packages/term_glyph
[gylph.ascii]: https://www.dartdocs.org/documentation/term_glyph/latest/term_glyph/ascii.html
26 changes: 26 additions & 0 deletions lib/src/descriptor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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';

/// A declarative description of a filesystem entry.
///
/// This may be extended outside this package.
abstract class Descriptor {
/// This entry's basename.
final String name;

Descriptor(this.name);

/// Creates this entry within the [parent] directory, which defaults to
/// [sandbox].
Future create([String parent]);

/// Validates that the physical file system under [parent] (which defaults to
/// [sandbox]) contains an entry that matches this descriptor.
Future validate([String parent]);

/// Returns a human-friendly tree-style description of this descriptor.
String describe();
}
140 changes: 140 additions & 0 deletions lib/src/directory_descriptor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// 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:io';

import 'package:async/async.dart';
import 'package:path/path.dart' as p;
import 'package:term_glyph/term_glyph.dart' as glyph;
import 'package:test/test.dart';

import 'descriptor.dart';
import 'file_descriptor.dart';
import 'sandbox.dart';
import 'utils.dart';

/// A descriptor describing a directory that may contain nested descriptors.
///
/// In addition to the normal descriptor methods, this has a [load] method that
/// allows it to be used as a virtual filesystem.
///
/// This may be extended outside this package.
class DirectoryDescriptor extends Descriptor {
/// Descriptors for entries in this directory.
///
/// This may be modified.
final List<Descriptor> contents;

DirectoryDescriptor(String name, Iterable<Descriptor> contents)
: contents = contents.toList(),
super(name);

/// Creates a directory descriptor named [name] that describes the physical
/// directory at [path].
factory DirectoryDescriptor.fromFilesystem(String name, String path) {
return new DirectoryDescriptor(name,
new Directory(path).listSync().map((entity) {
// Ignore hidden files.
if (p.basename(entity.path).startsWith(".")) return null;

if (entity is Directory) {
return new DirectoryDescriptor.fromFilesystem(
p.basename(entity.path), entity.path);
} else if (entity is File) {
return new FileDescriptor(
p.basename(entity.path), entity.readAsBytesSync());
}
// Ignore broken symlinks.
}).where((path) => path != null));
}

Future create([String parent]) async {
var fullPath = p.join(parent ?? sandbox, name);
await new Directory(fullPath).create(recursive: true);
await Future.wait(contents.map((entry) => entry.create(fullPath)));
}

Future validate([String parent]) async {
var fullPath = p.join(parent ?? sandbox, name);
if (!(await new Directory(fullPath).exists())) {
fail('Directory not found: "${prettyPath(fullPath)}".');
}

await waitAndReportErrors(
contents.map((entry) => entry.validate(fullPath)));
}

/// Treats this descriptor as a virtual filesystem and loads the binary
/// contents of the [FileDescriptor] at the given relative [url], which may be
/// a [Uri] or a [String].
///
/// The [parent] parameter should only be passed by subclasses of
/// [DirectoryDescriptor] that are recursively calling [load]. It's the
/// URL-format path of the directories that have been loaded so far.
Stream<List<int>> load(url, [String parents]) {
String path;
if (url is String) {
path = url;
} else if (url is Uri) {
path = url.toString();
} else {
throw new ArgumentError.value(url, "url", "must be a Uri or a String.");
}

if (!p.url.isWithin('.', path)) {
throw new ArgumentError.value(
url, "url", "must be relative and beneath the base URL.");
}

return StreamCompleter.fromFuture(new Future.sync(() {
var split = p.url.split(p.url.normalize(path));
var file = split.length == 1;
var matchingEntries = contents.where((entry) {
return entry.name == split.first &&
file
? entry is FileDescriptor
: entry is DirectoryDescriptor;
}).toList();

var type = file ? 'file' : 'directory';
var parentsAndSelf = parents == null ? name : p.url.join(parents, name);
if (matchingEntries.isEmpty) {
fail('Couldn\'t find a $type descriptor named "${split.first}" within '
'"$parentsAndSelf".');
} else if (matchingEntries.length > 1) {
fail('Found multiple $type descriptors named "${split.first}" within '
'"$parentsAndSelf".');
} else {
var remainingPath = split.sublist(1);
if (remainingPath.isEmpty) {
return (matchingEntries.first as FileDescriptor).readAsBytes();
} else {
return (matchingEntries.first as DirectoryDescriptor)
.load(p.url.joinAll(remainingPath), parentsAndSelf);
}
}
}));
}

String describe() {
if (contents.isEmpty) return name;

var buffer = new StringBuffer();
buffer.writeln(name);
for (var entry in contents.take(contents.length - 1)) {
var entryString = prefixLines(
entry.describe(), '${glyph.verticalLine} ',
first: '${glyph.teeRight}${glyph.horizontalLine}'
'${glyph.horizontalLine} ');
buffer.writeln(entryString);
}

var lastEntryString = prefixLines(contents.last.describe(), ' ',
first: '${glyph.bottomLeftCorner}${glyph.horizontalLine}'
'${glyph.horizontalLine} ');
buffer.write(lastEntryString);
return buffer.toString();
}
}
Loading