// Copyright (c) 2020, 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 'package:analyzer/dart/analysis/analysis_context_collection.dart'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/file_system/file_system.dart'; import 'package:analyzer/file_system/physical_file_system.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; /// Helper class to test that `test_all.dart` files are properly set up in the /// `analyzer` package (and related packages). class VerifyTests { /// Path to the package's `test` subdirectory. final String testDirPath; /// Paths to exclude from analysis completely. final List? excludedPaths; VerifyTests(this.testDirPath, {this.excludedPaths}); /// Build tests. void build() { var provider = PhysicalResourceProvider.INSTANCE; var collection = AnalysisContextCollection( resourceProvider: provider, includedPaths: [testDirPath], excludedPaths: excludedPaths); var contexts = collection.contexts; if (contexts.length != 1) { fail('The test directory contains multiple analysis contexts.'); } _buildTestsIn(contexts[0].currentSession, testDirPath, provider.getFolder(testDirPath)); } /// May be overridden in a derived class to indicate whether the test file or /// directory indicated by [resource] is so expensive to run that it shouldn't /// be included in `test_all.dart` files. /// /// Default behavior is not to consider any test files or directories /// expensive. bool isExpensive(Resource resource) => false; /// May be overridden in a derived class to indicate whether it is ok for a /// `test_all.dart` file in [folder] to import [uri], even if there is no /// corresponding test file inside [folder]. /// /// Default behavior is to allow imports of test framework URIs. bool isOkAsAdditionalTestAllImport(Folder folder, String uri) => const [ 'package:test/test.dart', 'package:test_reflective_loader/test_reflective_loader.dart' ].contains(uri); /// May be overridden in a derived class to indicate whether it is ok for /// a `test_all.dart` file to be missing from [folder]. /// /// Default beahvior is not to allow `test_all.dart` to be missing from any /// folder. bool isOkForTestAllToBeMissing(Folder folder) => false; void _buildTestsIn( AnalysisSession session, String testDirPath, Folder directory) { var testFileNames = []; File? testAllFile; var children = directory.getChildren(); children .sort((first, second) => first.shortName.compareTo(second.shortName)); for (var child in children) { if (child is Folder) { if (child.getChildAssumingFile('test_all.dart').exists && !isExpensive(child)) { testFileNames.add('${child.shortName}/test_all.dart'); } _buildTestsIn(session, testDirPath, child); } else if (child is File) { var name = child.shortName; if (name == 'test_all.dart') { testAllFile = child; } else if (name.endsWith('_test.dart') && !isExpensive(child)) { testFileNames.add(name); } } } var relativePath = path.relative(directory.path, from: testDirPath); test(relativePath, () { if (testFileNames.isEmpty) { return; } if (testAllFile == null) { if (!isOkForTestAllToBeMissing(directory)) { fail('Missing "test_all.dart" in $relativePath'); } else { // Ok that the `test_all.dart` file is missing; there's nothing else to // check. return; } } if (isOkForTestAllToBeMissing(directory)) { fail('Found "test_all.dart" in $relativePath but did not expect one'); } var result = session.getParsedUnit2(testAllFile.path); if (result is! ParsedUnitResult) { fail('Could not parse ${testAllFile.path}'); } var importedFiles = []; for (var directive in result.unit.directives) { if (directive is ImportDirective) { var uri = directive.uri.stringValue; if (uri == null) { fail('Invalid URI: $directive'); } importedFiles.add(uri); } } var missingFiles = []; for (var testFileName in testFileNames) { if (!importedFiles.contains(testFileName)) { missingFiles.add(testFileName); } } if (missingFiles.isNotEmpty) { fail('Tests missing from "test_all.dart": ${missingFiles.join(', ')}'); } var extraImports = []; for (var importedFile in importedFiles) { if (!testFileNames.contains(importedFile) && !isOkAsAdditionalTestAllImport(directory, importedFile)) { extraImports.add(importedFile); } } if (extraImports.isNotEmpty) { fail('Extra tests in "test_all.dart": ${extraImports.join(', ')}'); } }); } }