drift/drift_dev/test/analysis/test_utils.dart

226 lines
7.0 KiB
Dart
Raw Normal View History

import 'dart:io';
import 'dart:isolate';
import 'package:analyzer/dart/analysis/analysis_context.dart';
import 'package:analyzer/dart/analysis/analysis_context_collection.dart';
import 'package:analyzer/dart/analysis/results.dart';
2022-09-03 14:29:18 -07:00
import 'package:analyzer/dart/ast/ast.dart';
2022-08-30 13:50:07 -07:00
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/overlay_file_system.dart';
import 'package:analyzer/file_system/physical_file_system.dart';
import 'package:build/build.dart';
2022-09-18 12:52:36 -07:00
import 'package:drift/drift.dart';
import 'package:drift_dev/src/analysis/backend.dart';
2022-08-30 13:50:07 -07:00
import 'package:drift_dev/src/analysis/driver/driver.dart';
import 'package:drift_dev/src/analysis/driver/error.dart';
import 'package:drift_dev/src/analysis/driver/state.dart';
2022-09-18 12:52:36 -07:00
import 'package:drift_dev/src/analysis/results/results.dart';
2022-11-11 09:02:30 -08:00
import 'package:drift_dev/src/analysis/options.dart';
import 'package:logging/logging.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as p;
2022-12-25 15:23:04 -08:00
import 'package:pub_semver/pub_semver.dart';
2022-09-05 14:31:01 -07:00
import 'package:test/test.dart';
/// A [DriftBackend] implementation used for testing.
///
/// This backend has limited support for Dart analysis: [sourceContents] forming
/// the package `a` are available for analysis. In addition, `drift` and
/// `drift_dev` imports can be analyzed as well.
class TestBackend extends DriftBackend {
2022-09-05 14:31:01 -07:00
final Map<String, String> sourceContents;
2022-12-25 15:23:04 -08:00
final Iterable<String> analyzerExperiments;
2022-08-30 13:50:07 -07:00
late final DriftAnalysisDriver driver;
AnalysisContext? _dartContext;
2022-12-25 15:23:04 -08:00
TestBackend(
Map<String, String> sourceContents, {
DriftOptions options = const DriftOptions.defaults(),
this.analyzerExperiments = const Iterable.empty(),
}) : sourceContents = {
for (final entry in sourceContents.entries)
2022-09-05 14:31:01 -07:00
AssetId.parse(entry.key).uri.toString(): entry.value,
2022-08-30 13:50:07 -07:00
} {
driver = DriftAnalysisDriver(this, options);
}
2022-12-25 15:23:04 -08:00
factory TestBackend.inTest(
Map<String, String> sourceContents, {
DriftOptions options = const DriftOptions.defaults(),
Iterable<String> analyzerExperiments = const Iterable.empty(),
}) {
final backend = TestBackend(sourceContents,
options: options, analyzerExperiments: analyzerExperiments);
addTearDown(backend.dispose);
return backend;
}
2022-10-30 11:41:59 -07:00
static Future<FileState> analyzeSingle(String content,
{String asset = 'a|lib/a.drift',
DriftOptions options = const DriftOptions.defaults()}) {
final assetId = AssetId.parse(asset);
final backend = TestBackend.inTest({asset: content}, options: options);
return backend.driver.fullyAnalyze(assetId.uri);
}
2022-10-26 14:09:05 -07:00
void expectNoErrors() {
for (final file in driver.cache.knownFiles.values) {
expect(file.allErrors, isEmpty, reason: 'Error in ${file.ownUri}');
}
}
Future<void> _setupDartAnalyzer() async {
final provider = OverlayResourceProvider(PhysicalResourceProvider.INSTANCE);
// Analyze example sources against the drift sources from the current
// drift_dev test runner.
final uri = await Isolate.packageConfig;
final hostConfig =
PackageConfig.parseBytes(await File.fromUri(uri!).readAsBytes(), uri);
final testConfig = PackageConfig([
...hostConfig.packages,
Package('a', Uri.directory('/a/'), packageUriRoot: Uri.parse('lib/')),
]);
// Write package config used to analyze dummy sources
final configBuffer = StringBuffer();
PackageConfig.writeString(testConfig, configBuffer);
provider.setOverlay('/a/.dart_tool/package_config.json',
content: configBuffer.toString(), modificationStamp: 1);
// Also put sources into the overlay:
sourceContents.forEach((key, value) {
2022-09-05 14:31:01 -07:00
final uri = Uri.parse(key);
if (uri.scheme == 'package') {
final package = uri.pathSegments.first;
final path =
p.url.joinAll(['/$package/lib', ...uri.pathSegments.skip(1)]);
provider.setOverlay(path, content: value, modificationStamp: 1);
}
});
2022-12-25 15:23:04 -08:00
if (analyzerExperiments.isNotEmpty) {
final experiments = analyzerExperiments.join(', ');
provider.setOverlay(
'/a/analysis_options.yaml',
content: 'analyzer: {enable-experiment: [$experiments]}',
modificationStamp: 1,
);
}
final collection = AnalysisContextCollection(
2022-09-05 14:31:01 -07:00
includedPaths: ['/a'],
resourceProvider: provider,
);
_dartContext = collection.contexts.single;
}
Future<void> ensureHasDartAnalyzer() async {
if (_dartContext == null) {
await _setupDartAnalyzer();
}
}
@override
Logger get log => Logger.root;
2022-08-30 13:50:07 -07:00
@override
Uri resolveUri(Uri base, String uriString) {
return base.resolve(uriString);
}
@override
Future<String> readAsString(Uri uri) async {
return sourceContents[uri.toString()] ??
(throw StateError('No source for $uri'));
}
2022-10-26 14:09:05 -07:00
@override
Future<Never> resolveExpression(
Uri context, String dartExpression, Iterable<String> imports) async {
throw UnsupportedError('Not currently supported in tests');
}
2022-08-30 13:50:07 -07:00
@override
Future<LibraryElement> readDart(Uri uri) async {
await ensureHasDartAnalyzer();
final result =
await _dartContext!.currentSession.getLibraryByUri(uri.toString());
return (result as LibraryElementResult).element;
2022-08-30 13:50:07 -07:00
}
2022-09-03 14:29:18 -07:00
@override
2022-09-05 14:31:01 -07:00
Future<AstNode?> loadElementDeclaration(Element element) async {
final library = element.library;
if (library == null) return null;
final info = await library.session.getResolvedLibraryByElement(library);
if (info is ResolvedLibraryResult) {
return info.getElementDeclaration(element)?.node;
} else {
return null;
}
2022-09-03 14:29:18 -07:00
}
Future<void> dispose() async {}
2022-10-26 14:09:05 -07:00
Future<FileState> analyze(String uriString) {
return driver.fullyAnalyze(Uri.parse(uriString));
}
}
2022-08-30 13:50:07 -07:00
2022-09-18 12:52:36 -07:00
Matcher get hasNoErrors =>
isA<FileState>().having((e) => e.allErrors, 'allErrors', isEmpty);
Matcher returnsColumns(Map<String, DriftSqlType> columns) {
return _HasInferredColumnTypes(columns);
}
class _HasInferredColumnTypes extends CustomMatcher {
_HasInferredColumnTypes(dynamic expected)
: super('Select query with inferred columns', 'columns', expected);
@override
Object? featureValueOf(dynamic actual) {
if (actual is! SqlSelectQuery) {
return actual;
}
final resultSet = actual.resultSet;
return {
2022-12-26 13:45:35 -08:00
for (final column in resultSet.scalarColumns) column.name: column.sqlType
2022-09-18 12:52:36 -07:00
};
}
}
2022-08-30 13:50:07 -07:00
2022-09-10 07:34:17 -07:00
TypeMatcher<DriftAnalysisError> isDriftError(dynamic message) {
2022-08-30 13:50:07 -07:00
return isA<DriftAnalysisError>().having((e) => e.message, 'message', message);
}
2022-09-10 07:34:17 -07:00
2022-12-25 15:23:04 -08:00
final _version = RegExp(r'\d\.\d+\.\d+');
String? requireDart(String minimalVersion) {
final version =
Version.parse(_version.firstMatch(Platform.version)!.group(0)!);
final minimal = Version.parse(minimalVersion);
if (version < minimal) {
return 'This test requires SDK version $minimalVersion or later';
} else {
return null;
}
}
2022-09-10 07:34:17 -07:00
extension DriftErrorMatchers on TypeMatcher<DriftAnalysisError> {
TypeMatcher<DriftAnalysisError> withSpan(lexemeMatcher) {
return having((e) => e.span?.text, 'span.text', lexemeMatcher);
}
}