import 'dart:convert'; import 'dart:isolate'; import 'package:analyzer/dart/analysis/features.dart'; import 'package:analyzer/dart/analysis/utilities.dart'; import 'package:analyzer/file_system/memory_file_system.dart'; import 'package:build/build.dart'; import 'package:build/experiments.dart'; import 'package:build_resolvers/build_resolvers.dart'; import 'package:build_test/build_test.dart'; import 'package:crypto/crypto.dart'; import 'package:drift_dev/integrations/build.dart'; import 'package:glob/glob.dart'; import 'package:logging/logging.dart'; import 'package:package_config/package_config.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; final _resolvers = withEnabledExperiments(() => AnalyzerResolvers.sharedInstance, ['records']); BuilderOptions builderOptionsFromYaml(String yaml) { final map = loadYaml(yaml); return BuilderOptions((map as YamlMap).cast()); } Logger loggerThat(dynamic expectedLogs) { final logger = Logger.detached('drift_dev_test'); expect(logger.onRecord, expectedLogs); return logger; } TypeMatcher record(dynamic message) { return isA().having((e) => e.message, 'message', message); } final _packageConfig = Future(() async { final uri = await Isolate.packageConfig; if (uri == null) { throw UnsupportedError( 'Isolate running the build does not have a package config and no ' 'fallback has been provided'); } return await loadPackageConfigUri(uri); }); Future emulateDriftBuild({ required Map inputs, BuilderOptions options = const BuilderOptions({}), Logger? logger, bool modularBuild = false, }) async { _resolvers.reset(); logger ??= Logger.detached('emulateDriftBuild'); final writer = InMemoryAssetWriter(); final reader = MultiAssetReader([ WrittenAssetReader(writer), InMemoryAssetReader( rootPackage: 'a', sourceAssets: { for (final entry in inputs.entries) makeAssetId(entry.key): entry.value, }, ), await PackageAssetReader.currentIsolate(), ]); final readAssets = <(Type, String), Set>{}; final stages = [ preparingBuilder(options), discover(options), analyzer(options), modularBuild ? modular(options) : driftBuilderNotShared(options), driftCleanup(options), ]; for (final stage in stages) { if (stage is Builder) { // We might want to consider running these concurrently, but tests are // easier to debug when running builders in a serial order. for (final input in inputs.keys) { final inputId = makeAssetId(input); // Assets from other packages are visible, but we're not running // builders on them. if (inputId.package != 'a') continue; if (expectedOutputs(stage, inputId).isNotEmpty) { final readerForPhase = _TrackingAssetReader(reader); await runBuilder( stage, [inputId], readerForPhase, writer, _resolvers, logger: logger, packageConfig: await _packageConfig, ); readAssets.putIfAbsent( (stage.runtimeType, input), () => {}).addAll(readerForPhase.read); } } } else if (stage is PostProcessBuilder) { final deleted = []; for (final assetId in writer.assets.keys) { final shouldBuild = stage.inputExtensions.any((e) => assetId.path.endsWith(e)); if (shouldBuild) { await runPostProcessBuilder( stage, assetId, reader, writer, logger, addAsset: (_) {}, deleteAsset: deleted.add, ); } } deleted.forEach(writer.assets.remove); } } logger.clearListeners(); return DriftBuildResult(writer, readAssets); } class DriftBuildResult { final InMemoryAssetWriter writer; /// Asset ids read for each (builder, input id) combination. final Map<(Type, String), Set> readAssetsByBuilder; DriftBuildResult(this.writer, this.readAssetsByBuilder); Iterable get dartOutputs { return writer.assets.keys.where((e) { return e.extension == '.dart'; }); } void checkDartOutputs(Map outputs) { checkOutputs(outputs, dartOutputs, writer); } } class _TrackingAssetReader implements AssetReader { final AssetReader _inner; final Set read = {}; _TrackingAssetReader(this._inner); void _trackRead(AssetId id) { read.add(id); } @override Future canRead(AssetId id) { _trackRead(id); return _inner.canRead(id); } @override Future digest(AssetId id) { _trackRead(id); return _inner.digest(id); } @override Stream findAssets(Glob glob) { return _inner.findAssets(glob).map((id) { _trackRead(id); return id; }); } @override Future> readAsBytes(AssetId id) { _trackRead(id); return _inner.readAsBytes(id); } @override Future readAsString(AssetId id, {Encoding encoding = utf8}) { _trackRead(id); return _inner.readAsString(id, encoding: encoding); } } class IsValidDartFile extends CustomMatcher { IsValidDartFile(valueOrMatcher) : super( 'A syntactically-valid Dart source file', 'parsed unit', valueOrMatcher, ); @override Object? featureValueOf(actual) { final resourceProvider = MemoryResourceProvider(); if (actual is List) { resourceProvider.newFileWithBytes('/foo.dart', actual); } else if (actual is String) { resourceProvider.newFile('/foo.dart', actual); } else { throw 'Not a String or a List'; } return parseFile( path: '/foo.dart', featureSet: FeatureSet.fromEnableFlags2( sdkLanguageVersion: Version(3, 0, 0), flags: const [], ), resourceProvider: resourceProvider, throwIfDiagnostics: true, ).unit; } }