diff --git a/packages/ack_generator/test/integration/ack_infer_graph_test.dart b/packages/ack_generator/test/integration/ack_infer_graph_test.dart index 5e62973d..1b4f85a9 100644 --- a/packages/ack_generator/test/integration/ack_infer_graph_test.dart +++ b/packages/ack_generator/test/integration/ack_infer_graph_test.dart @@ -5,9 +5,10 @@ import 'package:build_test/build_test.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; +import '../test_utils/isolate_sources.dart'; + Future _expectOutput(String source, Matcher matcher) async { - final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); - await readerWriter.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); await testBuilder( ackModelBuilder(BuilderOptions.empty), {'test_pkg|lib/schema.dart': source}, @@ -18,8 +19,7 @@ Future _expectOutput(String source, Matcher matcher) async { } Future _expectFailure(String source, List messages) async { - final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); - await readerWriter.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); final seen = {}; await testBuilder( ackModelBuilder(BuilderOptions.empty), @@ -47,8 +47,7 @@ part 'schema.ack.g.dart'; void main() { test('rejects a codec type the library cannot name', () async { - final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); - await readerWriter.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); final errors = []; await testBuilder( ackModelBuilder(BuilderOptions.empty), diff --git a/packages/ack_generator/test/integration/class_model_graph_test.dart b/packages/ack_generator/test/integration/class_model_graph_test.dart index 93f65eaa..67972287 100644 --- a/packages/ack_generator/test/integration/class_model_graph_test.dart +++ b/packages/ack_generator/test/integration/class_model_graph_test.dart @@ -4,6 +4,8 @@ import 'package:build_test/build_test.dart'; import 'package:logging/logging.dart'; import 'package:test/test.dart'; +import '../test_utils/isolate_sources.dart'; + Future _expectFailure( String body, List messages, { @@ -11,8 +13,7 @@ Future _expectFailure( Map extraSources = const {}, Map allowedOutputs = const {}, }) async { - final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); - await readerWriter.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); final seen = {}; await testBuilder( ackModelBuilder(BuilderOptions.empty), @@ -35,8 +36,7 @@ Future _expectFailure( } Future _expectWarning(String body, List messages) async { - final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); - await readerWriter.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); final seen = {}; await testBuilder( ackModelBuilder(BuilderOptions.empty), @@ -1198,8 +1198,7 @@ final class Address with _\$AddressAck { ); test('allows split imports for a class-first model and its facade', () async { - final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); - await readerWriter.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); await testBuilder( ackModelBuilder(BuilderOptions.empty), { diff --git a/packages/ack_generator/test/test_utils/isolate_sources.dart b/packages/ack_generator/test/test_utils/isolate_sources.dart new file mode 100644 index 00000000..8a0c1ee1 --- /dev/null +++ b/packages/ack_generator/test/test_utils/isolate_sources.dart @@ -0,0 +1,30 @@ +import 'dart:typed_data'; + +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; + +final _isolateSources = _loadIsolateSources(); + +/// Loads immutable dependency bytes into a fresh filesystem for each case. +/// +/// Visible package files must stay unchanged during the test isolate. A new +/// test invocation reloads them. Fixtures and generated output are never cached. +Future createTestReaderWriter() async { + final sources = await _isolateSources; + final readerWriter = TestReaderWriter(rootPackage: 'test_pkg'); + for (final entry in sources.entries) { + // Seed inputs without recording them as builder outputs. writeBytes copies + // the bytes, so a case can also mutate its own read buffers independently. + readerWriter.testing.writeBytes(entry.key, entry.value); + } + return readerWriter; +} + +Future> _loadIsolateSources() async { + final readerWriter = TestReaderWriter(); + await readerWriter.testing.loadIsolateSources(); + return Map.unmodifiable({ + for (final id in readerWriter.testing.assets) + id: readerWriter.testing.readBytes(id).asUnmodifiableView(), + }); +} diff --git a/packages/ack_generator/test/test_utils/isolate_sources_test.dart b/packages/ack_generator/test/test_utils/isolate_sources_test.dart new file mode 100644 index 00000000..255ca259 --- /dev/null +++ b/packages/ack_generator/test/test_utils/isolate_sources_test.dart @@ -0,0 +1,127 @@ +import 'package:ack_generator/src/builder.dart'; +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:test/test.dart'; + +import 'isolate_sources.dart'; + +void main() { + test('preserves every visible dependency asset and its bytes', () async { + final original = TestReaderWriter(rootPackage: 'test_pkg'); + await original.testing.loadIsolateSources(); + final readerWriter = await createTestReaderWriter(); + + expect(original.testing.assets, isNotEmpty); + expect( + readerWriter.testing.assets, + unorderedEquals(original.testing.assets), + ); + for (final id in original.testing.assets) { + expect( + readerWriter.testing.readBytes(id), + original.testing.readBytes(id), + reason: 'Dependency bytes changed for $id', + ); + } + expect(readerWriter.testing.assetsRead, isEmpty); + expect(readerWriter.testing.assetsWritten, isEmpty); + }); + + test('reader mutations, outputs and tracking stay in their case', () async { + final first = await createTestReaderWriter(); + final dependency = AssetId('ack', 'lib/ack.dart'); + final removed = AssetId('ack', 'lib/annotations.dart'); + final fixture = AssetId('test_pkg', 'lib/schema.dart'); + final output = AssetId('test_pkg', 'lib/schema.ack.dart'); + final originalBytes = List.of(first.testing.readBytes(dependency)); + + await first.readAsBytes(dependency); + first.testing.readBytes(dependency)[0] ^= 0xff; + first.testing.delete(removed); + first.testing.writeString(fixture, 'final fixture = 1;'); + await first.writeAsString(output, 'generated in the first case'); + expect(first.testing.assetsRead, contains(dependency)); + expect(first.testing.assetsWritten, contains(output)); + + final second = await createTestReaderWriter(); + expect(second, isNot(same(first))); + expect(second.testing.readBytes(dependency), originalBytes); + expect(second.testing.exists(removed), isTrue); + expect(second.testing.exists(fixture), isFalse); + expect(second.testing.exists(output), isFalse); + expect(second.testing.assetsRead, isEmpty); + expect(second.testing.assetsWritten, isEmpty); + }); + + test( + 'concurrent requests get independent readers and byte buffers', + () async { + final readers = await Future.wait([ + createTestReaderWriter(), + createTestReaderWriter(), + ]); + final dependency = AssetId('ack', 'lib/ack.dart'); + final originalBytes = List.of( + readers[1].testing.readBytes(dependency), + ); + readers[0].testing.readBytes(dependency)[0] ^= 0xff; + + expect(readers[0], isNot(same(readers[1]))); + expect(readers[1].testing.readBytes(dependency), originalBytes); + final later = await createTestReaderWriter(); + expect(later.testing.readBytes(dependency), originalBytes); + }, + ); + + for (final validFirst in [true, false]) { + test('resolves fresh fixtures and outputs with valid case ' + '${validFirst ? 'before' : 'after'} a missing JSON part', () async { + for (final valid in [validFirst, !validFirst]) { + final readerWriter = await createTestReaderWriter(); + final errors = []; + expect( + readerWriter.testing.assets.where((id) => id.package == 'test_pkg'), + isEmpty, + ); + expect(readerWriter.testing.assetsRead, isEmpty); + expect(readerWriter.testing.assetsWritten, isEmpty); + + await testBuilder( + ackModelBuilder(BuilderOptions.empty), + { + 'test_pkg|lib/schema.dart': valid + ? _source + : _source.replaceFirst("part 'schema.ack.g.dart';", ''), + }, + generateFor: const {'test_pkg|lib/schema.dart'}, + readerWriter: readerWriter, + outputs: valid + ? { + 'test_pkg|lib/schema.ack.dart': decodedMatches( + contains('final class User'), + ), + } + : const {}, + onLog: (log) { + if (log.level.name == 'SEVERE') errors.add(log.message); + }, + ); + expect( + errors, + valid ? isEmpty : contains(contains("part 'schema.ack.g.dart';")), + ); + } + }); + } +} + +const _source = ''' +import 'package:ack/ack.dart'; +import 'package:ack/annotations.dart'; + +part 'schema.ack.dart'; +part 'schema.ack.g.dart'; + +@Schemable() +final userSchema = Ack.object({'name': Ack.string()}); +''';