This repository was archived by the owner on Apr 8, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 7
Initial code commit with basic functionality, some tests, and a README #1
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.packages | ||
.pub | ||
pubspec.lock |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
Want to contribute? Great! First, read this page (including the small print at | ||
the end). | ||
|
||
### Before you contribute | ||
|
||
Before we can use your code, you must sign the | ||
[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) | ||
(CLA), which you can do online. The CLA is necessary mainly because you own the | ||
copyright to your changes, even after your contribution becomes part of our | ||
codebase, so we need your permission to use and distribute your code. We also | ||
need to be sure of various other things—for instance that you'll tell us if you | ||
know that your code infringes on other people's patents. You don't have to sign | ||
the CLA until after you've submitted your code for review and a member has | ||
approved it, but you must do it before we can put your code into our codebase. | ||
|
||
Before you start working on a larger contribution, you should get in touch with | ||
us first through the issue tracker with your idea so that we can help out and | ||
possibly guide you. Coordinating up front makes it much easier to avoid | ||
frustration later on. | ||
|
||
### Code reviews | ||
|
||
All submissions, including submissions by project members, require review. | ||
|
||
### File headers | ||
|
||
All files in the project must start with the following header. | ||
|
||
// 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. | ||
|
||
### The small print | ||
|
||
Contributions made by corporations are covered by a different agreement than the | ||
one above, the | ||
[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
Keep multiline test case inputs and outputs out of your Dart test code, and | ||
write them directly as text files. | ||
|
||
## Example | ||
|
||
Take the following test case for String's `toUpperCase()`: | ||
|
||
```dart | ||
void main() { | ||
test('Works on multiline strings', () { | ||
var input = r'''This is the first line. | ||
This is another line. | ||
|
||
This is another paragraph.'''; | ||
|
||
var expectedOutput = r'''THIS IS THE FIRST LINE. | ||
THIS IS ANOTHER LINE. | ||
|
||
THIS IS ANOTHER PARAGRAPH.'''; | ||
|
||
expect(input.toUpperCase(), equals(expectedOutput)); | ||
}); | ||
} | ||
``` | ||
|
||
Multiline strings break the visual flow of code indentations. Additionally, when | ||
newline characters are important, you may not be able to just write a newline | ||
after `r'''`, or before the closing `''';`, which makes it hard to scan for | ||
where multiline Strings begin and end. Instead, let's add write this test case, | ||
and a few more, in a separate text file: | ||
|
||
```none | ||
>>> Works on simple strings | ||
This is a single line. | ||
<<< | ||
THIS IS A SINGLE LINE. | ||
>>> Does nothing to upper case strings | ||
THIS IS ALREADY UPPER CASE. | ||
<<< | ||
THIS IS ALREADY UPPER CASE. | ||
>>> Works on multiline strings | ||
This is the first line. | ||
This is another line. | ||
|
||
This is another paragraph. | ||
<<< | ||
THIS IS THE FIRST LINE. | ||
THIS IS ANOTHER LINE. | ||
|
||
THIS IS ANOTHER PARAGRAPH. | ||
``` | ||
|
||
We can quickly create tests over these data cases with some Dart: | ||
|
||
```dart | ||
library touppercase.tests; | ||
|
||
void main() { | ||
for (var dataCase in dataCasesUnder(library: #touppercase.tests)) { | ||
test(dataCase.testDescription, () { | ||
var actualOutput = dataCase.input.toUpperCase(); | ||
expect(actualOutput, equals(dataCase.expectedOutput)); | ||
}); | ||
} | ||
} | ||
``` | ||
|
||
If our test is located at `to_upper_case_package/test/to_upper_case_test.dart`, | ||
then `dataCasesUnder` will look for files ending in `.unit` in the same | ||
directory as the `library`. So our text file with the test cases should be, | ||
perhaps, `to_upper_case_package/test/cases.unit`. | ||
|
||
(Note: Why the weird library symbols? This is the simplest way to locate a | ||
directory or file relative to Dart source. Hopefully a temporary issue.) | ||
|
||
## When to use | ||
|
||
This package is not very broad purposed, and is probably appropriate for only | ||
very specific functionality testing. It has been very useful for testing | ||
packages that primarily transform one block of text into another block of text. | ||
|
||
This package is also mostly useful when the different ways to configure the | ||
processing is very limited. In these cases, the Dart code that receives the test | ||
cases and asserts over them can be very brief, and developers can focus on just | ||
writing the data test cases. | ||
|
||
This package is also most useful when whitespace is _ever_ significant. In cases | ||
like this, multiline strings cannot fake indentation, and it may be harder for | ||
the developer to track whitespace when writing a test case. In contrast, it is | ||
probably much easier to write input and expected output in simple text blocks | ||
in simple text files. Examples would include a Markdown parser that needs to | ||
parse indented list continuations or indented code blocks, and text formatters | ||
that need to test specific indentation in the output. | ||
|
||
Disclaimer: this is not an official Google product. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import 'dart:io'; | ||
import 'dart:mirrors'; | ||
|
||
import 'package:path/path.dart' as p; | ||
import 'package:test/test.dart'; | ||
|
||
// TODO(srawlins): add a recursive option | ||
/// Parse and yield data cases (each a [DataCase]) from [directory]. | ||
/// | ||
/// By default, only read data cases from files with a `.unit` extension. | ||
Iterable<DataCase> dataCases({ | ||
String directory, | ||
String extension: 'unit', | ||
}) sync* { | ||
var entries = | ||
new Directory(directory).listSync(recursive: false, followLinks: false); | ||
for (var entry in entries) { | ||
if (!entry.path.endsWith(extension)) { | ||
continue; | ||
} | ||
|
||
var file = | ||
p.basename(entry.path).replaceFirst(new RegExp('\.$extension\$'), ''); | ||
|
||
// Explicitly create a File, in case the entry is a Link. | ||
var lines = new File(entry.path).readAsLinesSync(); | ||
|
||
var i = 0; | ||
while (i < lines.length) { | ||
var description = | ||
lines[i++].replaceFirst(new RegExp(r'>>>\s*'), '').trim(); | ||
if (description == '') { | ||
description = 'line ${i+1}'; | ||
} else { | ||
description = 'line ${i+1}: $description'; | ||
} | ||
|
||
var input = ''; | ||
while (!lines[i].startsWith('<<<')) { | ||
input += lines[i++] + '\n'; | ||
} | ||
|
||
var expectedOutput = ''; | ||
while (++i < lines.length && !lines[i].startsWith('>>>')) { | ||
expectedOutput += lines[i] + '\n'; | ||
} | ||
|
||
var dataCase = new DataCase( | ||
directory: p.basename(directory), | ||
file: file, | ||
description: description, | ||
input: input, | ||
expectedOutput: expectedOutput); | ||
yield dataCase; | ||
} | ||
} | ||
} | ||
|
||
/// Parse and yield data cases (each a [DataCase]) from the directory containing | ||
/// [library]. | ||
/// | ||
/// By default, only read data cases from files with a `.unit` extension. | ||
/// | ||
/// The typical use case of this method is to declare a library at the top of a | ||
/// Dart test file, then reference the symbol with a pound sign. Example: | ||
/// | ||
/// ```dart | ||
/// library my_package.test.this_test; | ||
/// | ||
/// import 'package:expected_output/expected_output.dart'; | ||
/// import 'package:test/test.dart'; | ||
/// | ||
/// void main() { | ||
/// for (var dataCase in dataCasesUnder(library: #my_package.test.this_test)) { | ||
/// // ... | ||
/// } | ||
/// } | ||
/// ``` | ||
Iterable<DataCase> dataCasesUnder( | ||
{Symbol library, String extension: 'unit'}) sync* { | ||
var directory = | ||
p.dirname(currentMirrorSystem().findLibrary(library).uri.toFilePath()); | ||
for (var dataCase | ||
in expectedOutputs(directory: directory, extension: extension)) { | ||
yield dataCase; | ||
} | ||
} | ||
|
||
/// All of the data pertaining to a particular test case, namely the [input] and | ||
/// [expectedOutput]. | ||
class DataCase { | ||
final String directory; | ||
final String file; | ||
final String description; | ||
final String input; | ||
final String expectedOutput; | ||
|
||
DataCase( | ||
{this.directory, | ||
this.file, | ||
this.description, | ||
this.input, | ||
this.expectedOutput}); | ||
|
||
/// A good standard description for `test()`, derived from the data directory, | ||
/// the particular data file, and the test case description. | ||
String get testDescription => [directory, file, description].join(' '); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
>>> | ||
input 1 | ||
<<< | ||
output 1 | ||
>>> description with a space in the front. | ||
input 2 | ||
<<< | ||
output 2 | ||
>>>description without a space in the front. | ||
input 3 | ||
<<< | ||
output 3 | ||
>>> description with a few spaces in the front. | ||
input 4 | ||
<<< | ||
output 4 | ||
>>> multiline input and output | ||
input | ||
five | ||
<<< | ||
output | ||
five |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
>>> a second unit file | ||
an input | ||
<<< | ||
an output |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
library expected_output.test.simple_test; | ||
|
||
import 'dart:mirrors'; | ||
|
||
import 'package:expected_output/expected_output.dart'; | ||
import 'package:path/path.dart' as p; | ||
import 'package:test/test.dart'; | ||
|
||
void main() { | ||
var iterator; | ||
var dataCase; | ||
var iteratorIsEmpty; | ||
|
||
setUpAll(() { | ||
// Locate the "test" directory. Use mirrors so that this works with the test | ||
// package, which loads this suite into an isolate. | ||
var testDir = p.dirname(currentMirrorSystem() | ||
.findLibrary(#expected_output.test.simple_test) | ||
.uri | ||
.toFilePath()); | ||
iterator = dataCases(directory: p.join(testDir, 'simple_data')).iterator; | ||
}); | ||
|
||
setUp(() { | ||
iteratorIsEmpty = !iterator.moveNext(); | ||
dataCase = iterator.current; | ||
}); | ||
|
||
test('parses case w/o a description', () { | ||
expect(dataCase.directory, endsWith('simple_data')); | ||
expect(dataCase.file, 'cases'); | ||
expect(dataCase.description, 'line 2'); | ||
expect(dataCase.testDescription, 'simple_data cases line 2'); | ||
expect(dataCase.input, 'input 1\n'); | ||
expect(dataCase.expectedOutput, 'output 1\n'); | ||
}); | ||
|
||
test('parses case w/ whitespace after >>>', () { | ||
expect( | ||
dataCase.description, 'line 6: description with a space in the front.'); | ||
}); | ||
|
||
test('parses case w/o whitespace after >>>', () { | ||
expect(dataCase.description, | ||
'line 10: description without a space in the front.'); | ||
}); | ||
|
||
test('parses case w/ multiple whitespace after >>>', () { | ||
expect(dataCase.description, | ||
'line 14: description with a few spaces in the front.'); | ||
}); | ||
|
||
test('parses case w/ multiline input and output', () { | ||
expect(dataCase.input, 'input\nfive\n'); | ||
expect(dataCase.expectedOutput, 'output\nfive\n'); | ||
}); | ||
|
||
test('parses case w/o a description', () { | ||
expect(dataCase.directory, endsWith('simple_data')); | ||
expect(dataCase.file, 'cases2'); | ||
expect(dataCase.description, 'line 2: a second unit file'); | ||
expect(dataCase.testDescription, | ||
'simple_data cases2 line 2: a second unit file'); | ||
}); | ||
|
||
test('the dataCases iterator is empty at the end', () { | ||
expect(iteratorIsEmpty, isTrue); | ||
}); | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the benefit in using reflection vs. specifying a data directory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no other way to get a directory relative to the executing code. (Unless there recently is.) The mirrors incantation comes from dart-lang/test#110.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just put a little comment in the README about this.