mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

## Description This adds a checker that will check all of the API docs examples to make sure that they are linked from at least one source file in the packages. It also checks to make sure that all of the examples have an associated test. Since there are a large number that don't have tests at the moment, there is also a large exception list that can be burned down (burn down list is in https://github.com/flutter/flutter/issues/130459). Because there are missing links currently, this PR will only pass after https://github.com/flutter/flutter/pull/130521 is merged. ## Related Issues - https://github.com/flutter/flutter/issues/129956 - https://github.com/flutter/flutter/issues/130459 ## Tests - Added test for the checker.
191 lines
7.5 KiB
Dart
191 lines
7.5 KiB
Dart
// Copyright 2014 The Flutter Authors. 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:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
import '../check_code_samples.dart';
|
|
import '../utils.dart';
|
|
import 'common.dart';
|
|
|
|
void main() {
|
|
late SampleChecker checker;
|
|
late FileSystem fs;
|
|
late Directory examples;
|
|
late Directory packages;
|
|
late Directory dartUIPath;
|
|
late Directory flutterRoot;
|
|
|
|
String getRelativePath(File file, [Directory? from]) {
|
|
from ??= flutterRoot;
|
|
return path.relative(file.absolute.path, from: flutterRoot.absolute.path);
|
|
}
|
|
|
|
void writeLink({required File source, required File example}) {
|
|
source
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('''
|
|
/// Class documentation
|
|
///
|
|
/// {@tool dartpad}
|
|
/// Example description
|
|
///
|
|
/// ** See code in ${getRelativePath(example)} **
|
|
/// {@end-tool}
|
|
''');
|
|
}
|
|
|
|
void buildTestFiles({bool missingLinks = false, bool missingTests = false}) {
|
|
final Directory examplesLib = examples.childDirectory('lib').childDirectory('layer')..createSync(recursive: true);
|
|
final File fooExample = examplesLib.childFile('foo_example.0.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// Example for foo');
|
|
final File barExample = examplesLib.childFile('bar_example.0.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// Example for bar');
|
|
final File curvesExample =
|
|
examples.childDirectory('lib').childDirectory('animation').childDirectory('curves').childFile('curve2_d.0.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// Missing a test, but OK');
|
|
if (missingLinks) {
|
|
examplesLib.childFile('missing_example.0.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// Example that is not linked');
|
|
}
|
|
final Directory examplesTests = examples.childDirectory('test').childDirectory('layer')
|
|
..createSync(recursive: true);
|
|
examplesTests.childFile('foo_example.0_test.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// test for foo example');
|
|
if (!missingTests) {
|
|
examplesTests.childFile('bar_example.0_test.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// test for bar example');
|
|
}
|
|
if (missingLinks) {
|
|
examplesTests.childFile('missing_example.0_test.dart')
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('// test for foo example');
|
|
}
|
|
final Directory flutterPackage = packages.childDirectory('flutter').childDirectory('lib').childDirectory('src')
|
|
..createSync(recursive: true);
|
|
writeLink(source: flutterPackage.childDirectory('layer').childFile('foo.dart'), example: fooExample);
|
|
writeLink(source: flutterPackage.childDirectory('layer').childFile('bar.dart'), example: barExample);
|
|
writeLink(source: flutterPackage.childDirectory('animation').childFile('curves.dart'), example: curvesExample);
|
|
}
|
|
|
|
setUp(() {
|
|
fs = MemoryFileSystem(style: Platform.isWindows ? FileSystemStyle.windows : FileSystemStyle.posix);
|
|
// Get the root prefix of the current directory so that on Windows we get a
|
|
// correct root prefix.
|
|
flutterRoot = fs.directory(path.join(path.rootPrefix(fs.currentDirectory.absolute.path), 'flutter sdk'))..createSync(recursive: true);
|
|
fs.currentDirectory = flutterRoot;
|
|
examples = flutterRoot.childDirectory('examples').childDirectory('api')..createSync(recursive: true);
|
|
packages = flutterRoot.childDirectory('packages')..createSync(recursive: true);
|
|
dartUIPath = flutterRoot
|
|
.childDirectory('bin')
|
|
.childDirectory('cache')
|
|
.childDirectory('pkg')
|
|
.childDirectory('sky_engine')
|
|
.childDirectory('lib')
|
|
..createSync(recursive: true);
|
|
checker = SampleChecker(
|
|
examples: examples,
|
|
packages: packages,
|
|
dartUIPath: dartUIPath,
|
|
flutterRoot: flutterRoot,
|
|
filesystem: fs,
|
|
);
|
|
});
|
|
|
|
test('check_code_samples.dart - checkCodeSamples catches missing links', () async {
|
|
buildTestFiles(missingLinks: true);
|
|
bool? success;
|
|
final String result = await capture(
|
|
() async {
|
|
success = checker.checkCodeSamples();
|
|
},
|
|
shouldHaveErrors: true,
|
|
);
|
|
final String lines = <String>[
|
|
'╔═╡ERROR╞═══════════════════════════════════════════════════════════════════════',
|
|
'║ The following examples are not linked from any source file API doc comments:',
|
|
'║ examples/api/lib/layer/missing_example.0.dart',
|
|
'║ Either link them to a source file API doc comment, or remove them.',
|
|
'╚═══════════════════════════════════════════════════════════════════════════════',
|
|
].map((String line) {
|
|
return line.replaceAll('/', Platform.isWindows ? r'\' : '/');
|
|
}).join('\n');
|
|
expect(result, equals('$lines\n'));
|
|
expect(success, equals(false));
|
|
});
|
|
|
|
test('check_code_samples.dart - checkCodeSamples catches missing tests', () async {
|
|
buildTestFiles(missingTests: true);
|
|
bool? success;
|
|
final String result = await capture(
|
|
() async {
|
|
success = checker.checkCodeSamples();
|
|
},
|
|
shouldHaveErrors: true,
|
|
);
|
|
final String lines = <String>[
|
|
'╔═╡ERROR╞═══════════════════════════════════════════════════════════════════════',
|
|
'║ The following example test files are missing:',
|
|
'║ examples/api/test/layer/bar_example.0_test.dart',
|
|
'╚═══════════════════════════════════════════════════════════════════════════════',
|
|
].map((String line) {
|
|
return line.replaceAll('/', Platform.isWindows ? r'\' : '/');
|
|
}).join('\n');
|
|
expect(result, equals('$lines\n'));
|
|
expect(success, equals(false));
|
|
});
|
|
|
|
test('check_code_samples.dart - checkCodeSamples succeeds', () async {
|
|
buildTestFiles();
|
|
bool? success;
|
|
final String result = await capture(
|
|
() async {
|
|
success = checker.checkCodeSamples();
|
|
},
|
|
);
|
|
expect(result, isEmpty);
|
|
expect(success, equals(true));
|
|
});
|
|
}
|
|
|
|
typedef AsyncVoidCallback = Future<void> Function();
|
|
|
|
Future<String> capture(AsyncVoidCallback callback, {bool shouldHaveErrors = false}) async {
|
|
final StringBuffer buffer = StringBuffer();
|
|
final PrintCallback oldPrint = print;
|
|
try {
|
|
print = (Object? line) {
|
|
buffer.writeln(line);
|
|
};
|
|
await callback();
|
|
expect(
|
|
hasError,
|
|
shouldHaveErrors,
|
|
reason: buffer.isEmpty
|
|
? '(No output to report.)'
|
|
: hasError
|
|
? 'Unexpected errors:\n$buffer'
|
|
: 'Unexpected success:\n$buffer',
|
|
);
|
|
} finally {
|
|
print = oldPrint;
|
|
resetErrorStatus();
|
|
}
|
|
if (stdout.supportsAnsiEscapes) {
|
|
// Remove ANSI escapes when this test is running on a terminal.
|
|
return buffer.toString().replaceAll(RegExp(r'(\x9B|\x1B\[)[0-?]{1,3}[ -/]*[@-~]'), '');
|
|
} else {
|
|
return buffer.toString();
|
|
}
|
|
}
|