// 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, String? alternateLink}) { final String link = alternateLink ?? ' ** See code in ${getRelativePath(example)} **'; source ..createSync(recursive: true) ..writeAsStringSync(''' /// Class documentation /// /// {@tool dartpad} /// Example description /// ///$link /// {@end-tool} '''); } void buildTestFiles({bool missingLinks = false, bool missingTests = false, bool malformedLinks = 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); if (malformedLinks) { writeLink(source: flutterPackage.childDirectory('layer').childFile('foo.dart'), example: fooExample, alternateLink: '*See Code *'); writeLink(source: flutterPackage.childDirectory('layer').childFile('bar.dart'), example: barExample, alternateLink: ' ** See code examples/api/lib/layer/bar_example.0.dart **'); writeLink(source: flutterPackage.childDirectory('animation').childFile('curves.dart'), example: curvesExample, alternateLink: '* see code in examples/api/lib/animation/curves/curve2_d.0.dart *'); } else { 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 #1╞════════════════════════════════════════════════════════════════════', '║ 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 malformed links', () async { buildTestFiles(malformedLinks: true); bool? success; final String result = await capture( () async { success = checker.checkCodeSamples(); }, shouldHaveErrors: true, ); final bool isWindows = Platform.isWindows; final String lines = <String>[ '╔═╡ERROR #1╞════════════════════════════════════════════════════════════════════', '║ The following examples are not linked from any source file API doc comments:', if (!isWindows) '║ examples/api/lib/animation/curves/curve2_d.0.dart', if (!isWindows) '║ examples/api/lib/layer/foo_example.0.dart', if (!isWindows) '║ examples/api/lib/layer/bar_example.0.dart', if (isWindows) r'║ examples\api\lib\animation\curves\curve2_d.0.dart', if (isWindows) r'║ examples\api\lib\layer\foo_example.0.dart', if (isWindows) r'║ examples\api\lib\layer\bar_example.0.dart', '║ Either link them to a source file API doc comment, or remove them.', '╚═══════════════════════════════════════════════════════════════════════════════', '╔═╡ERROR #2╞════════════════════════════════════════════════════════════════════', '║ The following malformed links were found in API doc comments:', if (!isWindows) '║ /flutter sdk/packages/flutter/lib/src/animation/curves.dart:6: ///* see code in examples/api/lib/animation/curves/curve2_d.0.dart *', if (!isWindows) '║ /flutter sdk/packages/flutter/lib/src/layer/foo.dart:6: ///*See Code *', if (!isWindows) '║ /flutter sdk/packages/flutter/lib/src/layer/bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **', if (isWindows) r'║ C:\flutter sdk\packages\flutter\lib\src\animation\curves.dart:6: ///* see code in examples/api/lib/animation/curves/curve2_d.0.dart *', if (isWindows) r'║ C:\flutter sdk\packages\flutter\lib\src\layer\foo.dart:6: ///*See Code *', if (isWindows) r'║ C:\flutter sdk\packages\flutter\lib\src\layer\bar.dart:6: /// ** See code examples/api/lib/layer/bar_example.0.dart **', '║ Correct the formatting of these links so that they match the exact pattern:', r"║ r'\*\* See code in (?<path>.+) \*\*'", '╚═══════════════════════════════════════════════════════════════════════════════', ].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 #1╞════════════════════════════════════════════════════════════════════', '║ 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(); } }