blob: c0e04fe351d3ce25ea1bf0a98bedaecbe7af85b1 [file] [edit]
// Copyright (c) 2026, 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/features.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/file_system/file_system.dart';
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart'; // ignore: implementation_imports
import 'package:analyzer/src/dart/analysis/byte_store.dart'; // ignore: implementation_imports
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart'; // ignore: implementation_imports
import 'package:analyzer/src/generated/engine.dart' // ignore: implementation_imports
show AnalysisEngine;
import 'package:analyzer/src/test_utilities/mock_sdk.dart'; // ignore: implementation_imports
import 'package:analyzer/src/test_utilities/platform.dart'; // ignore: implementation_imports
import 'package:analyzer/src/util/file_paths.dart' // ignore: implementation_imports
as file_paths;
import 'package:analyzer_testing/configuration_files_mixin.dart';
import 'package:analyzer_testing/experiments/experiments.dart';
import 'package:analyzer_testing/mock_packages/mock_packages.dart';
import 'package:analyzer_testing/resource_provider_mixin.dart';
import 'package:analyzer_testing/utilities/utilities.dart';
import 'package:meta/meta.dart';
/// A base class for analyzer and analysis server tests.
///
/// Sets up an in-memory file system with a stub SDK, package configuration,
/// and default analysis options. Provides helper methods and properties for
/// creating test files, configuring analysis contexts, and obtaining resolved
/// ASTs.
class AbstractContextTest
with MockPackagesMixin, ConfigurationFilesMixin, ResourceProviderMixin {
/// The byte store that is reused between tests.
///
/// This allows reusing all unlinked and linked summaries for SDK, so that
/// tests run much faster. However nothing is preserved between Dart VM runs,
/// so changes to the implementation are still fully verified.
static final MemoryByteStore _sharedByteStore = MemoryByteStore();
final MemoryByteStore _byteStore = _sharedByteStore;
final Map<String, String> _declaredVariables = {};
AnalysisContextCollectionImpl? _analysisContextCollection;
/// The file system path for `analysis_options.yaml` in [testPackageRootPath].
String get analysisOptionsPath =>
'$testPackageRootPath/analysis_options.yaml';
/// The [ByteStore] reused between tests.
ByteStore get byteStore => _byteStore;
/// The [AnalysisContextCollection] managing the analysis contexts.
AnalysisContextCollection get contextCollection {
_createAnalysisContexts();
return _analysisContextCollection!;
}
/// The file system path to the mock SDK root directory.
@override
String get dartSdkPath => sdkRoot.path;
/// The line terminator being used for test files and to be expected in edits.
String get eol => testEol;
/// Return a list of the experimental features that are to be enabled for
/// tests in this class.
List<Feature> get experimentalFeatures => experimentalFeaturesForTests;
/// The path that is not in [workspaceRootPath], contains external packages.
@override
String get packagesRootPath => '/packages';
/// The [Folder] representing the stub SDK root directory.
///
/// Can be overridden by subclasses that require a custom SDK setup.
Folder get sdkRoot => newFolder('/sdk');
/// The [AnalysisSession] for the test package's [testFile], after applying
/// any pending file changes.
Future<AnalysisSession> get session async {
var analysisContext = contextFor2(testFile);
await analysisContext.applyPendingFileChanges();
return analysisContext.currentSession;
}
/// The default [File] for testing, located at [testFilePath].
///
/// Most single-file tests write source code to this file.
File get testFile => getFile(testFilePath);
/// The name of the test file.
String get testFileName => 'test.dart';
/// The file system path of [testFile].
String get testFilePath => '$testPackageLibPath/$testFileName';
/// The file system path of the `lib` directory of the package-under-test.
String get testPackageLibPath => '$testPackageRootPath/lib';
/// The file system path for `pubspec.yaml` in [testPackageRootPath].
String get testPackagePubspecPath => testPubspecPath;
/// The file system path of the root of the package-under-test.
@override
String get testPackageRootPath => '$workspaceRootPath/test';
/// The file system path to the `test` directory of the package-under-test.
///
/// Rarely used except when a test specifically tests files situated inside a
/// package's `test` directory.
String get testPackageTestPath => '$testPackageRootPath/test';
/// The file system path for `pubspec.yaml` in [testPackageRootPath].
String get testPubspecPath => '$testPackageRootPath/pubspec.yaml';
/// The file system path of the root of the workspace.
String get workspaceRootPath => '/home';
List<String> get _collectionIncludedPaths => [workspaceRootPath];
/// Returns the existing analysis context that should be used to analyze the
/// given [path], or throw [StateError] if the [path] is not analyzed in any
/// of the created analysis contexts.
@Deprecated("Use 'contextFor2'")
DriverBasedAnalysisContext contextFor(String path) {
_createAnalysisContexts();
var convertedPath = convertPath(path);
return _analysisContextCollection!.contextFor(convertedPath);
}
/// Returns the existing analysis context that should be used to analyze the
/// given [file], or throw [StateError] if the [file] is not analyzed in any
/// of the created analysis contexts.
DriverBasedAnalysisContext contextFor2(File file) {
_createAnalysisContexts();
return _analysisContextCollection!.contextFor(file.path);
}
/// Create an analysis options file based on the given arguments.
void createAnalysisOptionsFile({
List<String> includes = const [],
List<Feature>? experimentalFeatures,
List<String> legacyPlugins = const [],
List<String> cannotIgnore = const [],
List<String> lints = const [],
Map<String, Object?> errors = const {},
bool propagateLinterExceptions = true,
bool strictCasts = false,
bool strictInference = false,
bool strictRawTypes = false,
}) {
writeAnalysisOptionsFile(
analysisOptionsContent(
includes: includes,
experimentalFeatures: experimentalFeatures ?? this.experimentalFeatures,
legacyPlugins: legacyPlugins,
propagateLinterExceptions: propagateLinterExceptions,
rules: lints,
errors: errors,
unignorableNames: cannotIgnore,
strictCasts: strictCasts,
strictInference: strictInference,
strictRawTypes: strictRawTypes,
),
);
}
/// Resolves and returns the [ResolvedUnitResult] for the given [file].
Future<ResolvedUnitResult> getResolvedUnit(File file) async {
return resolveFile(file.path);
}
/// Creates or updates a file at [path] with the given [content].
///
/// Line endings in [content] are normalized using [normalizeSource], and the
/// file is added to relevant analysis drivers if analysis contexts have
/// already been created.
///
/// Throws a [StateError] if a non-Dart file is modified after the analysis
/// context collection has already been initialized.
@override
File newFile(String path, String content) {
if (_analysisContextCollection != null && !path.endsWith('.dart')) {
throw StateError('Only dart files can be changed after analysis.');
}
var file = super.newFile(path, normalizeSource(content));
_addAnalyzedFileToDrivers(file);
return file;
}
/// Normalizes newlines in [code] for the current platform.
String normalizeSource(String code) => normalizeNewlinesForPlatform(code);
/// Resolves a Dart source file at [filePath].
Future<ResolvedUnitResult> resolveFile(String filePath) async {
var file = getFile(filePath);
var analysisContext = contextFor2(file);
await analysisContext.applyPendingFileChanges();
var session = analysisContext.currentSession;
return await session.getResolvedUnit(file.path) as ResolvedUnitResult;
}
/// Initializes the test environment before each test.
@mustCallSuper
void setUp() {
createMockSdk(resourceProvider: resourceProvider, root: sdkRoot);
writeTestPackageConfig2();
createAnalysisOptionsFile(experimentalFeatures: experimentalFeatures);
}
/// Disposes of the test environment after each test.
///
/// Clears analysis engine caches and disposes of the analysis context
/// collection.
@mustCallSuper
Future<void> tearDown() async {
AnalysisEngine.instance.clearCaches();
await _analysisContextCollection?.dispose();
_analysisContextCollection = null;
}
/// Updates `pubspec.yaml` and creates the driver.
void updateTestPubspecFile(String content) {
newFile(testPubspecPath, content);
}
/// Writes string content as an analysis options file.
void writeAnalysisOptionsFile(String content) {
newFile(analysisOptionsPath, content);
}
void _addAnalyzedFilesToDrivers() {
for (var analysisContext in _analysisContextCollection!.contexts) {
for (var path in analysisContext.contextRoot.analyzedFiles()) {
if (file_paths.isDart(resourceProvider.pathContext, path)) {
analysisContext.driver.addFile(path);
}
}
}
}
void _addAnalyzedFileToDrivers(File file) {
var path = file.path;
var collection = _analysisContextCollection;
if (collection != null) {
for (var analysisContext in collection.contexts) {
if (analysisContext.contextRoot.isAnalyzed(path)) {
analysisContext.driver.addFile(path);
}
}
}
}
/// Create all analysis contexts in [_collectionIncludedPaths].
void _createAnalysisContexts() {
if (_analysisContextCollection != null) {
return;
}
_analysisContextCollection = AnalysisContextCollectionImpl(
byteStore: _byteStore,
declaredVariables: _declaredVariables,
enableIndex: true,
includedPaths: _collectionIncludedPaths.map(convertPath).toList(),
resourceProvider: resourceProvider,
sdkPath: sdkRoot.path,
withFineDependencies: true,
);
_addAnalyzedFilesToDrivers();
}
}