| // Copyright (c) 2015, 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. |
| |
| library dartdoc.dartdoc_test; |
| |
| import 'dart:async'; |
| import 'dart:io'; |
| import 'dart:mirrors'; |
| |
| import 'package:dartdoc/dartdoc.dart'; |
| import 'package:dartdoc/src/logging.dart'; |
| import 'package:dartdoc/src/model.dart'; |
| import 'package:dartdoc/src/tuple.dart'; |
| import 'package:dartdoc/src/warnings.dart'; |
| import 'package:path/path.dart' as pathLib; |
| import 'package:test/test.dart'; |
| |
| import 'src/utils.dart'; |
| |
| Uri get _currentFileUri => |
| (reflect(main) as ClosureMirror).function.location.sourceUri; |
| String get _testPackagePath => |
| pathLib.fromUri(_currentFileUri.resolve('../testing/test_package')); |
| String get _testPackageFlutterPluginPath => pathLib |
| .fromUri(_currentFileUri.resolve('../testing/test_package_flutter_plugin')); |
| |
| class DartdocLoggingOptionContext extends DartdocGeneratorOptionContext |
| with LoggingContext { |
| DartdocLoggingOptionContext(DartdocOptionSet optionSet, Directory dir) |
| : super(optionSet, dir); |
| } |
| |
| void main() { |
| group('dartdoc with generators', () { |
| Directory tempDir; |
| List<String> outputParam; |
| |
| setUpAll(() async { |
| tempDir = Directory.systemTemp.createTempSync('dartdoc.test.'); |
| outputParam = ['--output', tempDir.path]; |
| DartdocOptionSet optionSet = await DartdocOptionSet.fromOptionGenerators( |
| 'dartdoc', [createLoggingOptions]); |
| optionSet.parseArguments([]); |
| startLogging( |
| new DartdocLoggingOptionContext(optionSet, Directory.current)); |
| }); |
| |
| tearDown(() async { |
| tempDir.listSync().forEach((FileSystemEntity f) { |
| f.deleteSync(recursive: true); |
| }); |
| }); |
| |
| Future<Dartdoc> buildDartdoc( |
| List<String> argv, Directory packageRoot) async { |
| return await Dartdoc.withDefaultGenerators(await generatorContextFromArgv( |
| argv..addAll(['--input', packageRoot.path])..addAll(outputParam))); |
| } |
| |
| group('Option handling', () { |
| Dartdoc dartdoc; |
| DartdocResults results; |
| PackageGraph p; |
| |
| setUpAll(() async { |
| dartdoc = await buildDartdoc([], testPackageOptions); |
| results = await dartdoc.generateDocsBase(); |
| p = results.packageGraph; |
| }); |
| |
| test('generator parameters', () async { |
| File favicon = new File( |
| pathLib.joinAll([tempDir.path, 'static-assets', 'favicon.png'])); |
| File index = new File(pathLib.joinAll([tempDir.path, 'index.html'])); |
| expect(favicon.readAsStringSync(), |
| contains('Not really a png, but a test file')); |
| String indexString = index.readAsStringSync(); |
| expect(indexString, contains('Footer things')); |
| expect(indexString, contains('footer.txt data')); |
| expect(indexString, contains('HTML header file')); |
| }); |
| |
| test('examplePathPrefix', () async { |
| Class UseAnExampleHere = p.allCanonicalModelElements |
| .whereType<Class>() |
| .firstWhere((ModelElement c) => c.name == 'UseAnExampleHere'); |
| expect( |
| UseAnExampleHere.documentationAsHtml, |
| contains( |
| 'An example of an example in an unusual example location.')); |
| }); |
| |
| test('includeExternal and showUndocumentedCategories', () async { |
| Class Something = p.allCanonicalModelElements |
| .whereType<Class>() |
| .firstWhere((ModelElement c) => c.name == 'Something'); |
| expect(Something.isPublic, isTrue); |
| expect(Something.displayedCategories, isNotEmpty); |
| }); |
| }); |
| |
| test('errors generate errors even when warnings are off', () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageToolError); |
| DartdocResults results = await dartdoc.generateDocsBase(); |
| PackageGraph p = results.packageGraph; |
| Iterable<String> unresolvedToolErrors = p |
| .packageWarningCounter.countedWarnings.values |
| .expand<String>((Set<Tuple2<PackageWarning, String>> s) => s |
| .where((Tuple2<PackageWarning, String> t) => |
| t.item1 == PackageWarning.toolError) |
| .map<String>((Tuple2<PackageWarning, String> t) => t.item2)); |
| |
| expect(p.packageWarningCounter.errorCount, equals(1)); |
| expect(unresolvedToolErrors.length, equals(1)); |
| expect(unresolvedToolErrors.first, |
| contains('Tool "drill" returned non-zero exit code')); |
| }); |
| |
| group('Invoking command-line dartdoc', () { |
| String dartdocPath = |
| pathLib.canonicalize(pathLib.join('bin', 'dartdoc.dart')); |
| CoverageSubprocessLauncher subprocessLauncher; |
| |
| setUpAll(() { |
| subprocessLauncher = |
| new CoverageSubprocessLauncher('dartdoc_test-subprocesses'); |
| }); |
| |
| tearDownAll(() async { |
| await Future.wait(CoverageSubprocessLauncher.coverageResults); |
| }); |
| |
| test('errors cause non-zero exit when warnings are off', () async { |
| expect( |
| () => subprocessLauncher.runStreamed(Platform.resolvedExecutable, [ |
| dartdocPath, |
| '--input=${testPackageToolError.path}', |
| '--output=${pathLib.join(tempDir.absolute.path, 'test_package_tool_error')}' |
| ]), |
| throwsA(const TypeMatcher<ProcessException>())); |
| }); |
| |
| test('errors cause non-zero exit when warnings are on', () async { |
| expect( |
| () => subprocessLauncher.runStreamed(Platform.resolvedExecutable, [ |
| dartdocPath, |
| '--input=${testPackageToolError.path}', |
| '--output=${pathLib.join(tempDir.absolute.path, 'test_package_tool_error')}', |
| '--show-warnings', |
| ]), |
| throwsA(const TypeMatcher<ProcessException>())); |
| }); |
| |
| test('help prints command line args', () async { |
| List<String> outputLines = []; |
| await subprocessLauncher.runStreamed( |
| Platform.resolvedExecutable, [dartdocPath, '--help'], |
| perLine: outputLines.add); |
| expect(outputLines, |
| contains('Generate HTML documentation for Dart libraries.')); |
| expect( |
| outputLines.join('\n'), |
| contains(new RegExp('^-h, --help[ ]+Show command help.', |
| multiLine: true))); |
| }); |
| |
| test('Validate missing FLUTTER_ROOT exception is clean', () async { |
| StringBuffer output = new StringBuffer(); |
| var args = <String>[dartdocPath]; |
| Future run = subprocessLauncher.runStreamed( |
| Platform.resolvedExecutable, args, |
| environment: new Map.from(Platform.environment) |
| ..remove('FLUTTER_ROOT'), |
| includeParentEnvironment: false, |
| workingDirectory: _testPackageFlutterPluginPath, perLine: (s) { |
| output.writeln(s); |
| }); |
| // Asynchronous exception, but we still need the output, too. |
| expect(run, throwsA(new TypeMatcher<ProcessException>())); |
| try { |
| await run; |
| } on ProcessException catch (_) {} |
| |
| expect( |
| output.toString(), |
| contains(new RegExp( |
| 'Top level package requires Flutter but FLUTTER_ROOT environment variable not set|test_package_flutter_plugin requires the Flutter SDK, version solving failed'))); |
| expect(output.toString(), isNot(contains('asynchronous gap'))); |
| }); |
| |
| test("Validate --version works", () async { |
| StringBuffer output = new StringBuffer(); |
| var args = <String>[dartdocPath, '--version']; |
| await subprocessLauncher.runStreamed(Platform.resolvedExecutable, args, |
| workingDirectory: _testPackagePath, |
| perLine: (s) => output.writeln(s)); |
| PackageMeta dartdocMeta = new PackageMeta.fromFilename(dartdocPath); |
| expect(output.toString(), |
| endsWith('dartdoc version: ${dartdocMeta.version}\n')); |
| }); |
| |
| test('Check for sample code in examples', () async { |
| StringBuffer output = new StringBuffer(); |
| var args = <String>[ |
| dartdocPath, |
| '--include', |
| 'ex', |
| '--no-include-source', |
| '--output', |
| tempDir.path |
| ]; |
| |
| await subprocessLauncher.runStreamed(Platform.resolvedExecutable, args, |
| workingDirectory: _testPackagePath, |
| perLine: (s) => output.writeln(s)); |
| |
| // Examples are reported as unfound because we (purposefully) |
| // did not use --example-path-prefix above. |
| final sep = '.'; // We don't care what the path separator character is |
| final firstUnfoundExample = |
| new RegExp('warning: lib${sep}example.dart: ' |
| '@example file not found.*test_package${sep}dog${sep}food.md'); |
| if (!output.toString().contains(firstUnfoundExample)) { |
| fail('Should warn about unfound @example files'); |
| } |
| }); |
| |
| test('Validate JSON output', () async { |
| var args = <String>[ |
| dartdocPath, |
| '--include', |
| 'ex', |
| '--no-include-source', |
| '--output', |
| tempDir.path, |
| '--json' |
| ]; |
| |
| Iterable<Map> jsonValues = await subprocessLauncher.runStreamed( |
| Platform.resolvedExecutable, args, |
| workingDirectory: _testPackagePath); |
| |
| expect(jsonValues, isNotEmpty, |
| reason: 'All STDOUT lines should be JSON-encoded maps.'); |
| }, timeout: new Timeout.factor(2)); |
| |
| test('--footer-text includes text', () async { |
| String footerTextPath = |
| pathLib.join(Directory.systemTemp.path, 'footer.txt'); |
| new File(footerTextPath).writeAsStringSync(' footer text include '); |
| |
| var args = <String>[ |
| dartdocPath, |
| '--footer-text=${footerTextPath}', |
| '--include', |
| 'ex', |
| '--output', |
| tempDir.path |
| ]; |
| |
| await subprocessLauncher.runStreamed(Platform.resolvedExecutable, args, |
| workingDirectory: _testPackagePath); |
| |
| File outFile = new File(pathLib.join(tempDir.path, 'index.html')); |
| expect(outFile.readAsStringSync(), contains('footer text include')); |
| }); |
| }, timeout: new Timeout.factor(3)); |
| |
| group('Option handling with cross-linking', () { |
| DartdocResults results; |
| Package testPackageOptions; |
| |
| setUpAll(() async { |
| results = await (await buildDartdoc( |
| ['--link-to-remote'], testPackageOptionsImporter)) |
| .generateDocsBase(); |
| testPackageOptions = results.packageGraph.packages |
| .firstWhere((Package p) => p.name == 'test_package_options'); |
| }); |
| |
| test('linkToUrl', () async { |
| Library main = testPackageOptions.allLibraries |
| .firstWhere((Library l) => l.name == 'main'); |
| Class UseAnExampleHere = main.allClasses |
| .firstWhere((Class c) => c.name == 'UseAnExampleHere'); |
| expect(testPackageOptions.documentedWhere, |
| equals(DocumentLocation.remote)); |
| expect( |
| UseAnExampleHere.href, |
| equals( |
| 'https://nonexistingsuperpackage.topdomain/test_package_options/0.0.1/main/UseAnExampleHere-class.html')); |
| }); |
| |
| test('includeExternal works via remote', () async { |
| Library unusualLibrary = testPackageOptions.allLibraries |
| .firstWhere((Library l) => l.name == 'unusualLibrary'); |
| expect( |
| unusualLibrary.allClasses |
| .firstWhere((Class c) => c.name == 'Something'), |
| isNotNull); |
| }); |
| }); |
| |
| test('with broken reexport chain', () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageImportExportError); |
| DartdocResults results = await dartdoc.generateDocsBase(); |
| PackageGraph p = results.packageGraph; |
| Iterable<String> unresolvedExportWarnings = p |
| .packageWarningCounter.countedWarnings.values |
| .expand<String>((Set<Tuple2<PackageWarning, String>> s) => s |
| .where((Tuple2<PackageWarning, String> t) => |
| t.item1 == PackageWarning.unresolvedExport) |
| .map<String>((Tuple2<PackageWarning, String> t) => t.item2)); |
| |
| expect(unresolvedExportWarnings.length, equals(1)); |
| expect(unresolvedExportWarnings.first, |
| equals('"package:not_referenced_in_pubspec/library3.dart"')); |
| }); |
| |
| group('include/exclude parameters', () { |
| test('with config file', () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageIncludeExclude); |
| DartdocResults results = await dartdoc.generateDocs(); |
| PackageGraph p = results.packageGraph; |
| expect(p.localPublicLibraries.map((l) => l.name), |
| orderedEquals(['explicitly_included', 'more_included'])); |
| }); |
| |
| test('with include command line argument', () async { |
| Dartdoc dartdoc = await buildDartdoc( |
| ['--include', 'another_included'], testPackageIncludeExclude); |
| DartdocResults results = await dartdoc.generateDocs(); |
| PackageGraph p = results.packageGraph; |
| expect(p.localPublicLibraries.length, equals(1)); |
| expect(p.localPublicLibraries.first.name, equals('another_included')); |
| }); |
| |
| test('with exclude command line argument', () async { |
| Dartdoc dartdoc = await buildDartdoc( |
| ['--exclude', 'more_included'], testPackageIncludeExclude); |
| DartdocResults results = await dartdoc.generateDocs(); |
| PackageGraph p = results.packageGraph; |
| expect(p.localPublicLibraries.length, equals(1)); |
| expect( |
| p.localPublicLibraries.first.name, equals('explicitly_included')); |
| }); |
| }); |
| |
| test('package without version produces valid semver in docs', () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageMinimumDir); |
| DartdocResults results = await dartdoc.generateDocs(); |
| PackageGraph p = results.packageGraph; |
| expect(p.defaultPackage.version, equals('0.0.0-unknown')); |
| }); |
| |
| test('basic interlinking test', () async { |
| Dartdoc dartdoc = |
| await buildDartdoc(['--link-to-remote'], testPackageDir); |
| DartdocResults results = await dartdoc.generateDocs(); |
| PackageGraph p = results.packageGraph; |
| Package meta = p.publicPackages.firstWhere((p) => p.name == 'meta'); |
| TopLevelVariable useSomethingInAnotherPackage = p.publicLibraries |
| .firstWhere((l) => l.name == 'fake') |
| .properties |
| .firstWhere((p) => p.name == 'useSomethingInAnotherPackage'); |
| TopLevelVariable useSomethingInTheSdk = p.publicLibraries |
| .firstWhere((l) => l.name == 'fake') |
| .properties |
| .firstWhere((p) => p.name == 'useSomethingInTheSdk'); |
| expect(meta.documentedWhere, equals(DocumentLocation.remote)); |
| expect( |
| useSomethingInAnotherPackage.modelType.linkedName, |
| matches( |
| '<a href=\"https://pub.dartlang.org/documentation/meta/[^\"]*/meta/Required-class.html\">Required</a>')); |
| RegExp stringLink = new RegExp( |
| 'https://api.dartlang.org/(dev|stable|edge|be)/${Platform.version.split(' ').first}/dart-core/String-class.html">String</a>'); |
| expect(useSomethingInTheSdk.modelType.linkedName, contains(stringLink)); |
| }); |
| |
| test('generate docs for ${pathLib.basename(testPackageDir.path)} works', |
| () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageDir); |
| |
| DartdocResults results = await dartdoc.generateDocs(); |
| expect(results.packageGraph, isNotNull); |
| |
| PackageGraph packageGraph = results.packageGraph; |
| Package p = packageGraph.defaultPackage; |
| expect(p.name, 'test_package'); |
| expect(p.hasDocumentationFile, isTrue); |
| // Total number of public libraries in test_package. |
| expect(packageGraph.defaultPackage.publicLibraries, hasLength(12)); |
| expect(packageGraph.localPackages.length, equals(1)); |
| }); |
| |
| test('generate docs for ${pathLib.basename(testPackageBadDir.path)} fails', |
| () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageBadDir); |
| |
| try { |
| await dartdoc.generateDocs(); |
| fail('dartdoc should fail on analysis errors'); |
| } catch (e) { |
| expect(e is DartdocFailure, isTrue); |
| } |
| }, |
| skip: 'Blocked on getting analysis errors with correct interpretation' |
| 'from analysis_options'); |
| |
| test('generate docs for a package that does not have a readme', () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageWithNoReadme); |
| |
| DartdocResults results = await dartdoc.generateDocs(); |
| expect(results.packageGraph, isNotNull); |
| |
| PackageGraph p = results.packageGraph; |
| expect(p.defaultPackage.name, 'test_package_small'); |
| expect(p.defaultPackage.hasHomepage, isFalse); |
| expect(p.defaultPackage.hasDocumentationFile, isFalse); |
| expect(p.localPublicLibraries, hasLength(1)); |
| }); |
| |
| test('generate docs including a single library', () async { |
| Dartdoc dartdoc = |
| await buildDartdoc(['--include', 'fake'], testPackageDir); |
| |
| DartdocResults results = await dartdoc.generateDocs(); |
| expect(results.packageGraph, isNotNull); |
| |
| PackageGraph p = results.packageGraph; |
| expect(p.defaultPackage.name, 'test_package'); |
| expect(p.defaultPackage.hasDocumentationFile, isTrue); |
| expect(p.libraries, hasLength(1)); |
| expect(p.libraries.map((lib) => lib.name), contains('fake')); |
| }); |
| |
| test('generate docs excluding a single library', () async { |
| Dartdoc dartdoc = |
| await buildDartdoc(['--exclude', 'fake'], testPackageDir); |
| |
| DartdocResults results = await dartdoc.generateDocs(); |
| expect(results.packageGraph, isNotNull); |
| |
| PackageGraph p = results.packageGraph; |
| expect(p.defaultPackage.name, 'test_package'); |
| expect(p.defaultPackage.hasDocumentationFile, isTrue); |
| expect(p.localPublicLibraries, hasLength(11)); |
| expect(p.localPublicLibraries.map((lib) => lib.name).contains('fake'), |
| isFalse); |
| }); |
| |
| test('generate docs for package with embedder yaml', () async { |
| Dartdoc dartdoc = await buildDartdoc([], testPackageWithEmbedderYaml); |
| |
| DartdocResults results = await dartdoc.generateDocs(); |
| expect(results.packageGraph, isNotNull); |
| |
| PackageGraph p = results.packageGraph; |
| expect(p.defaultPackage.name, 'test_package_embedder_yaml'); |
| expect(p.defaultPackage.hasDocumentationFile, isFalse); |
| expect(p.libraries, hasLength(3)); |
| expect(p.libraries.map((lib) => lib.name).contains('dart:core'), isTrue); |
| expect(p.libraries.map((lib) => lib.name).contains('dart:async'), isTrue); |
| expect(p.libraries.map((lib) => lib.name).contains('dart:bear'), isTrue); |
| expect(p.packageMap.length, equals(2)); |
| // Things that do not override the core SDK belong in their own package. |
| expect(p.packageMap["Dart"].isSdk, isTrue); |
| expect(p.packageMap["test_package_embedder_yaml"].isSdk, isFalse); |
| // Should be true once dart-lang/sdk#32707 is fixed. |
| //expect( |
| // p.publicLibraries, |
| // everyElement((Library l) => |
| // (l.element as LibraryElement).isInSdk == l.packageMeta.isSdk)); |
| // Ensure that we actually parsed some source by checking for |
| // the 'Bear' class. |
| Library dart_bear = p.packageMap["Dart"].libraries |
| .firstWhere((lib) => lib.name == 'dart:bear'); |
| expect( |
| dart_bear.allClasses.map((cls) => cls.name).contains('Bear'), isTrue); |
| expect(p.packageMap["Dart"].publicLibraries, hasLength(3)); |
| }); |
| }, timeout: new Timeout.factor(8)); |
| } |