| // Copyright (c) 2019, 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:io'; | |
| import 'package:path/path.dart' as p; | |
| /// Parse and yield data cases (each a [DataCase]) from [path]. | |
| Iterable<DataCase> dataCasesInFile({ | |
| required String path, | |
| String? baseDir, | |
| }) sync* { | |
| final file = p.basename(path).replaceFirst(RegExp(r'\..+$'), ''); | |
| baseDir ??= p.relative(p.dirname(path), from: p.dirname(p.dirname(path))); | |
| // Explicitly create a File, in case the entry is a Link. | |
| final lines = File(path).readAsLinesSync(); | |
| final frontMatter = StringBuffer(); | |
| var i = 0; | |
| while (!lines[i].startsWith('>>>')) { | |
| frontMatter.write('${lines[i++]}\n'); | |
| } | |
| while (i < lines.length) { | |
| var description = lines[i++].replaceFirst(RegExp(r'>>>\s*'), '').trim(); | |
| final skip = description.startsWith('skip:'); | |
| if (description == '') { | |
| description = 'line ${i + 1}'; | |
| } else { | |
| description = 'line ${i + 1}: $description'; | |
| } | |
| final input = StringBuffer(); | |
| while (!lines[i].startsWith('<<<')) { | |
| input.writeln(lines[i++]); | |
| } | |
| final expectedOutput = StringBuffer(); | |
| while (++i < lines.length && !lines[i].startsWith('>>>')) { | |
| expectedOutput.writeln(lines[i]); | |
| } | |
| final dataCase = DataCase( | |
| directory: baseDir, | |
| file: file, | |
| front_matter: frontMatter.toString(), | |
| description: description, | |
| skip: skip, | |
| input: input.toString(), | |
| expectedOutput: expectedOutput.toString(), | |
| ); | |
| yield dataCase; | |
| } | |
| } | |
| /// Parse and return data cases (each a [DataCase]) from [directory]. | |
| /// | |
| /// By default, only read data cases from files with a `.unit` extension. Data | |
| /// cases are read from files located immediately in [directory], or | |
| /// recursively, according to [recursive]. | |
| Iterable<DataCase> _dataCases({ | |
| required String directory, | |
| String extension = 'unit', | |
| bool recursive = true, | |
| }) { | |
| final entries = Directory( | |
| directory, | |
| ).listSync(recursive: recursive, followLinks: false); | |
| final results = <DataCase>[]; | |
| for (final entry in entries) { | |
| if (!entry.path.endsWith(extension)) { | |
| continue; | |
| } | |
| final relativeDir = p.relative( | |
| p.dirname(entry.path), | |
| from: p.dirname(directory), | |
| ); | |
| results.addAll(dataCasesInFile(path: entry.path, baseDir: relativeDir)); | |
| } | |
| // The API makes no guarantees on order. This is just here for stability in | |
| // tests. | |
| results.sort((a, b) { | |
| final compare = a.directory.compareTo(b.directory); | |
| if (compare != 0) return compare; | |
| return a.file.compareTo(b.file); | |
| }); | |
| return results; | |
| } | |
| /// Parse and yield data cases (each a [DataCase]) from [testDirectory]. | |
| /// | |
| /// By default, only read data cases from files with a `.unit` extension. Data | |
| /// cases are read from files located immediately in [testDirectory], or | |
| /// recursively, according to [recursive]. | |
| /// | |
| /// 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 (final dataCase | |
| /// in dataCasesUnder(library: #my_package.test.this_test)) { | |
| /// // ... | |
| /// } | |
| /// } | |
| /// ``` | |
| Iterable<DataCase> dataCasesUnder({ | |
| required String testDirectory, | |
| String extension = 'unit', | |
| bool recursive = true, | |
| }) sync* { | |
| final directory = p.join(p.current, 'test', testDirectory); | |
| for (final dataCase in _dataCases( | |
| directory: directory, | |
| extension: extension, | |
| recursive: recursive, | |
| )) { | |
| 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; | |
| // ignore: non_constant_identifier_names | |
| final String front_matter; | |
| final String description; | |
| final bool skip; | |
| final String input; | |
| final String expectedOutput; | |
| DataCase({ | |
| this.directory = '', | |
| this.file = '', | |
| // ignore: non_constant_identifier_names | |
| this.front_matter = '', | |
| this.description = '', | |
| this.skip = false, | |
| required this.input, | |
| required 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(' '); | |
| } |