blob: fe2b26841eac4364f247acf55f3d6f3a510a242b [file] [log] [blame]
// 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 test_utils;
import 'dart:io';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/file_system/memory_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:analyzer/src/dart/sdk/sdk.dart';
import 'package:analyzer/src/test_utilities/mock_sdk.dart';
import 'package:dartdoc/options.dart';
import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/failure.dart';
import 'package:dartdoc/src/generator/generator.dart';
import 'package:dartdoc/src/generator/resource_loader.dart';
import 'package:dartdoc/src/markdown_processor.dart';
import 'package:dartdoc/src/matching_link_result.dart';
import 'package:dartdoc/src/model/model_element.dart';
import 'package:dartdoc/src/model/package_builder.dart';
import 'package:dartdoc/src/model/package_graph.dart';
import 'package:dartdoc/src/package_config_provider.dart';
import 'package:dartdoc/src/package_meta.dart';
import 'package:dartdoc/src/warnings.dart';
import 'package:path/path.dart' as p;
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
/// The number of public libraries in testing/test_package, minus 2 for
/// the excluded libraries listed in the initializers for _testPackageGraphMemo
/// and minus 1 for the <nodoc> tag in the 'excluded' library.
const int kTestPackagePublicLibraries = 27;
final _resourceProvider = pubPackageMetaProvider.resourceProvider;
final _pathContext = _resourceProvider.pathContext;
final String platformVersionString = Platform.version.split(' ').first;
final Version platformVersion = Version.parse(platformVersionString);
final Folder testPackageToolError = _resourceProvider.getFolder(_pathContext
.absolute(_pathContext.normalize('testing/test_package_tool_error')));
/// Convenience factory to build a [DartdocOptionContext] and associate it with a
/// [DartdocOptionSet] based on the current working directory.
Future<DartdocOptionContext> contextFromArgv(
List<String> argv, PackageMetaProvider packageMetaProvider) async {
var optionSet = DartdocOptionRoot.fromOptionGenerators(
'dartdoc', [createDartdocOptions], packageMetaProvider);
optionSet.parseArguments(argv);
return DartdocOptionContext.fromDefaultContextLocation(
optionSet, pubPackageMetaProvider.resourceProvider);
}
/// Convenience factory to build a [DartdocGeneratorOptionContext] and
/// associate it with a [DartdocOptionSet] based on the current working
/// directory and/or the '--input' flag.
Future<DartdocGeneratorOptionContext> generatorContextFromArgv(
List<String> argv, PackageMetaProvider packageMetaProvider) async {
var optionSet = DartdocOptionRoot.fromOptionGenerators(
'dartdoc',
[
createDartdocOptions,
createGeneratorOptions,
],
packageMetaProvider);
optionSet.parseArguments(argv);
return DartdocGeneratorOptionContext.fromDefaultContextLocation(
optionSet, packageMetaProvider.resourceProvider);
}
void runPubGet(String dirPath) {
var binPath = p.join(p.dirname(Platform.resolvedExecutable), 'dart');
if (Platform.isWindows) {
binPath += '.exe';
}
var result = Process.runSync(
binPath,
['pub', 'get'],
workingDirectory: dirPath,
);
if (result.exitCode != 0) {
var buf = StringBuffer();
buf.writeln('${result.stdout}');
buf.writeln('${result.stderr}');
throw DartdocFailure('pub get failed: ${buf.toString().trim()}');
}
}
/// Creates a package at [dirPath] and returns the [PackageGraph] built with
/// [PubPackageBuilder.buildPackageGraph].
Future<PackageGraph> bootBasicPackage(
String dirPath,
PackageMetaProvider packageMetaProvider,
PackageConfigProvider packageConfigProvider, {
List<String> excludeLibraries = const [],
List<String> additionalArguments = const [],
bool skipUnreachableSdkLibraries = true,
}) async {
final resourceProvider = packageMetaProvider.resourceProvider;
if (resourceProvider == PhysicalResourceProvider.INSTANCE) {
runPubGet(dirPath);
}
final dir = resourceProvider.getFolder(resourceProvider.pathContext
.absolute(resourceProvider.pathContext.normalize(dirPath)));
return PubPackageBuilder(
await contextFromArgv([
'--input',
dir.path,
'--sdk-dir',
packageMetaProvider.defaultSdkDir.path,
'--exclude',
excludeLibraries.join(','),
'--allow-tools',
...additionalArguments,
], packageMetaProvider),
packageMetaProvider,
packageConfigProvider,
skipUnreachableSdkLibraries: skipUnreachableSdkLibraries,
).buildPackageGraph();
}
/// Returns a [FakePackageConfigProvider] with an entry for the SDK directory.
FakePackageConfigProvider getTestPackageConfigProvider(String sdkPath) {
var packageConfigProvider = FakePackageConfigProvider();
// To build the package graph, we always ask package_config for a
// [PackageConfig] for the SDK directory. Put a dummy entry in.
packageConfigProvider.addPackageToConfigFor(
sdkPath, 'analyzer', Uri.file('/sdk/pkg/analyzer/'));
return packageConfigProvider;
}
/// Returns a [PackageMetaProvider] using a [MemoryResourceProvider].
PackageMetaProvider get testPackageMetaProvider {
var resourceProvider = MemoryResourceProvider();
final sdkRoot = resourceProvider.getFolder(
resourceProvider.pathContext
.canonicalize(resourceProvider.convertPath('/sdk')),
);
createMockSdk(
resourceProvider: resourceProvider,
root: sdkRoot,
);
writeMockSdkFiles(sdkRoot);
return PackageMetaProvider(
PubPackageMeta.fromElement,
PubPackageMeta.fromFilename,
PubPackageMeta.fromDir,
resourceProvider,
sdkRoot,
{},
defaultSdk: FolderBasedDartSdk(resourceProvider, sdkRoot),
messageForMissingPackageMeta: PubPackageMeta.messageForMissingPackageMeta,
);
}
/// Writes additional files for a mock SDK.
///
/// Included is a "version" file and an "api_readme.md" file.
void writeMockSdkFiles(Folder sdkFolder) {
sdkFolder.getChildAssumingFile('version').writeAsStringSync('2.16.0');
sdkFolder.getChildAssumingFile('api_readme.md').writeAsStringSync(
'Welcome to the [Dart](https://dart.dev/) API reference documentation');
_writeMockSdkBinFiles(sdkFolder);
}
/// Dartdoc has a few indicator files it uses to verify that a directory
/// represents a Dart SDK. These include "bin/dart" and "bin/pub".
void _writeMockSdkBinFiles(Folder root) {
var sdkBinFolder = root.getChildAssumingFolder('bin');
sdkBinFolder.getChildAssumingFile('dart').writeAsStringSync('');
var sdkIncludeFolder = root.getChildAssumingFolder('include');
sdkIncludeFolder.getChildAssumingFile('dart_version.h').writeAsStringSync('');
}
/// Writes a package named [packageName], with [resourceProvider], to the
/// "/projects" directory.
///
/// The package is added to [packageConfigProvider]. A standard pubspec is
/// written if one is not provided via [pubspecContent].
Folder writePackage(String packageName, MemoryResourceProvider resourceProvider,
FakePackageConfigProvider packageConfigProvider,
{String? pubspecContent}) {
pubspecContent ??= '''
name: $packageName
version: 0.0.1
homepage: https://github.com/dart-lang
''';
var pathContext = resourceProvider.pathContext;
var projectsFolder = resourceProvider.getFolder(
pathContext.canonicalize(resourceProvider.convertPath('/projects')));
var projectFolder = projectsFolder.getChildAssumingFolder(packageName)
..create();
var projectRoot = projectFolder.path;
projectFolder
.getChildAssumingFile('pubspec.yaml')
.writeAsStringSync(pubspecContent);
projectFolder.getChildAssumingFile('.packages').writeAsStringSync('''
# Generated by pub on 2020-07-07 08:25:30.557406.
one:../one/lib/
two:lib/
''');
projectFolder
.getChildAssumingFolder('.dart_tool')
.getChildAssumingFile('package_config.json')
.writeAsStringSync('''
{
"configVersion": 2,
"packages": [
{
"name": "one",
"rootUri": "../../one",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "two",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "2.0"
}
],
"generated": "2020-07-07T15:25:30.566271Z",
"generator": "pub",
"generatorVersion": "2.8.4"
}
''');
projectFolder.getChildAssumingFolder('lib').create();
packageConfigProvider.addPackageToConfigFor(projectRoot, packageName,
Uri.file('$projectRoot${resourceProvider.pathContext.separator}'));
return projectFolder;
}
Future<void> writeDartdocResources(ResourceProvider resourceProvider) async {
for (var template in [
'_accessor_getter',
'_accessor_setter',
'_callable',
'_callable_multiline',
'_categorization',
'_class',
'_constant',
'_documentation',
'_extension',
'_features',
'_feature_set',
'_footer',
'_head',
'_library',
'_mixin',
'_name_summary',
'_packages',
'_property',
'_search_sidebar',
'_sidebar_for_category',
'_sidebar_for_container',
'_sidebar_for_library',
'_source_code',
'_source_link',
'_super_chain',
'_type',
'_typedef',
'_type_multiline',
'_typedef_multiline',
'404error',
'category',
'class',
'constructor',
'enum',
'extension',
'function',
'index',
'library',
'method',
'mixin',
'property',
'top_level_property',
'typedef',
]) {
await resourceProvider.writeDartdocResource(
'templates/html/$template.html', 'CONTENT');
}
for (var resource in [
'docs.dart.js',
'docs.dart.js.map',
'favicon.png',
'search.svg',
'github.css',
'highlight.pack.js',
'play_button.svg',
'readme.md',
'styles.css',
]) {
await resourceProvider.writeDartdocResource(
'resources/$resource', 'CONTENT');
}
}
/// For comparison purposes, return an equivalent [MatchingLinkResult]
/// for the defining element returned. May return [originalResult].
/// We do this to eliminate canonicalization effects from comparison,
/// as the original lookup code returns canonicalized results and the
/// new lookup code is only guaranteed to return equivalent results.
MatchingLinkResult definingLinkResult(MatchingLinkResult originalResult) {
var definingReferable =
originalResult.commentReferable?.definingCommentReferable;
if (definingReferable != null &&
definingReferable != originalResult.commentReferable) {
return MatchingLinkResult(definingReferable);
}
return originalResult;
}
MatchingLinkResult referenceLookup(Warnable element, String codeRef) =>
definingLinkResult(getMatchingLinkElement(codeRef, element));
/// Returns a matcher which compresses consecutive whitespace in [text] into a
/// single space.
Matcher matchesCompressed(String text) => matches(RegExp(text.replaceAll(
RegExp(r'\s\s+', multiLine: true),
' *',
)));
/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
bool get enhancedEnumsAllowed =>
VersionRange(min: Version.parse('2.17.0-0'), includeMin: true)
.allows(platformVersion);
/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
bool get namedArgumentsAnywhereAllowed =>
VersionRange(min: Version.parse('2.17.0-0'), includeMin: true)
.allows(platformVersion);
/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
bool get recordsAllowed =>
VersionRange(min: Version.parse('2.19.0-0'), includeMin: true)
.allows(platformVersion);
/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
bool get classModifiersAllowed =>
VersionRange(min: Version.parse('3.0.0-0.0-dev'), includeMin: true)
.allows(platformVersion);
/// We can not use [ExperimentalFeature.releaseVersion] or even
/// [ExperimentalFeature.experimentalReleaseVersion] as these are set to `null`
/// even when partial analyzer implementations are available.
bool get superParametersAllowed =>
VersionRange(min: Version.parse('2.17.0-0'), includeMin: true)
.allows(platformVersion);
extension ModelElementIterableExtensions<T extends ModelElement>
on Iterable<T> {
T named(String name) => firstWhere((e) => e.name == name);
}
extension IterableStringExtensions on Iterable<String> {
/// The main content line of `this`.
Iterable<String> get mainContent =>
skipWhile((line) => !line.contains('"dartdoc-main-content"'))
.takeWhile((line) => !line.contains('/.main-content'));
/// Verifies that [mainContent] contains [matchers] in order.
void expectMainContentContainsAllInOrder(Iterable<Object?> matchers) {
expect(
mainContent,
containsAllInOrder(matchers),
reason: 'main content:\n\n${mainContent.join('\n')}',
);
}
}
/// Extension methods just for tests.
extension on ResourceProvider {
Future<void> writeDartdocResource(String path, String content) async {
var fileUri = await resolveResourceUri(Uri.parse('package:dartdoc/$path'));
getFile(fileUri.toFilePath()).writeAsStringSync(content);
}
}