Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> _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},
Expand All @@ -18,8 +19,7 @@ Future<void> _expectOutput(String source, Matcher matcher) async {
}

Future<void> _expectFailure(String source, List<String> messages) async {
final readerWriter = TestReaderWriter(rootPackage: 'test_pkg');
await readerWriter.testing.loadIsolateSources();
final readerWriter = await createTestReaderWriter();
final seen = <String>{};
await testBuilder(
ackModelBuilder(BuilderOptions.empty),
Expand Down Expand Up @@ -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 = <String>[];
await testBuilder(
ackModelBuilder(BuilderOptions.empty),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import 'package:build_test/build_test.dart';
import 'package:logging/logging.dart';
import 'package:test/test.dart';

import '../test_utils/isolate_sources.dart';

Future<void> _expectFailure(
String body,
List<String> messages, {
String head = _head,
Map<String, String> extraSources = const {},
Map<String, Object> allowedOutputs = const {},
}) async {
final readerWriter = TestReaderWriter(rootPackage: 'test_pkg');
await readerWriter.testing.loadIsolateSources();
final readerWriter = await createTestReaderWriter();
final seen = <String>{};
await testBuilder(
ackModelBuilder(BuilderOptions.empty),
Expand All @@ -35,8 +36,7 @@ Future<void> _expectFailure(
}

Future<void> _expectWarning(String body, List<String> messages) async {
final readerWriter = TestReaderWriter(rootPackage: 'test_pkg');
await readerWriter.testing.loadIsolateSources();
final readerWriter = await createTestReaderWriter();
final seen = <String>{};
await testBuilder(
ackModelBuilder(BuilderOptions.empty),
Expand Down Expand Up @@ -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),
{
Expand Down
30 changes: 30 additions & 0 deletions packages/ack_generator/test/test_utils/isolate_sources.dart
Original file line number Diff line number Diff line change
@@ -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<TestReaderWriter> 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<Map<AssetId, Uint8List>> _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(),
});
}
127 changes: 127 additions & 0 deletions packages/ack_generator/test/test_utils/isolate_sources_test.dart
Original file line number Diff line number Diff line change
@@ -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<int>.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<int>.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 = <String>[];
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()});
''';
Loading