Skip to content

Commit 8d83144

Browse files
committed
Add Filesystem underneath Reader, simplify tests further.
1 parent c959516 commit 8d83144

32 files changed

+366
-244
lines changed

_test_common/lib/in_memory_reader_writer.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class InMemoryRunnerAssetReaderWriter extends InMemoryAssetReaderWriter
1616
Stream<AssetId> get onCanRead => _onCanReadController.stream;
1717
void Function(AssetId)? onDelete;
1818

19-
InMemoryRunnerAssetReaderWriter({super.sourceAssets, super.rootPackage});
19+
InMemoryRunnerAssetReaderWriter({super.rootPackage});
2020

2121
@override
2222
Future<bool> canRead(AssetId id) {

_test_common/lib/test_phases.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ Future<TestBuildersResult> testBuilders(
113113
inputs.forEach((serializedId, contents) {
114114
var id = makeAssetId(serializedId);
115115
if (contents is String) {
116-
readerWriter.cacheStringAsset(id, contents);
116+
readerWriter.filesystem.writeAsStringSync(id, contents);
117117
} else if (contents is List<int>) {
118-
readerWriter.cacheBytesAsset(id, contents);
118+
readerWriter.filesystem.writeAsBytesSync(id, contents);
119119
}
120120
});
121121

build/lib/src/builder/build_step_impl.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import '../asset/writer.dart';
2020
import '../resource/resource.dart';
2121
import '../state/asset_finder.dart';
2222
import '../state/asset_path_provider.dart';
23+
import '../state/filesystem.dart';
2324
import '../state/input_tracker.dart';
2425
import '../state/reader_state.dart';
2526
import 'build_step.dart';
@@ -82,6 +83,9 @@ class BuildStepImpl implements BuildStep, AssetReaderState {
8283
_stageTracker = stageTracker ?? NoOpStageTracker.instance,
8384
_reportUnusedAssets = reportUnusedAssets;
8485

86+
@override
87+
Filesystem get filesystem => _reader.filesystem;
88+
8589
@override
8690
AssetFinder get assetFinder => _reader.assetFinder;
8791

build/lib/src/internal.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ library;
88

99
export 'state/asset_finder.dart';
1010
export 'state/asset_path_provider.dart';
11+
export 'state/filesystem.dart';
1112
export 'state/input_tracker.dart';
1213
export 'state/reader_state.dart';

build/lib/src/state/filesystem.dart

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:convert';
6+
import 'dart:io';
7+
import 'dart:typed_data';
8+
9+
import 'package:pool/pool.dart';
10+
11+
import '../asset/id.dart';
12+
import 'asset_path_provider.dart';
13+
14+
/// The filesystem the build is running on.
15+
abstract interface class Filesystem {
16+
Future<bool> exists(AssetId id);
17+
18+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8});
19+
Future<Uint8List> readAsBytes(AssetId id);
20+
21+
Future<void> delete(AssetId id);
22+
void deleteSync(AssetId id);
23+
24+
void writeAsStringSync(AssetId id, String contents,
25+
{Encoding encoding = utf8});
26+
Future<void> writeAsString(AssetId id, String contents,
27+
{Encoding encoding = utf8});
28+
29+
void writeAsBytesSync(AssetId id, List<int> contents);
30+
Future<void> writeAsBytes(AssetId id, List<int> contents);
31+
}
32+
33+
/// A filesystem using [assetPathProvider] to map to the `dart:io` filesystem.
34+
class IoFilesystem implements Filesystem {
35+
final AssetPathProvider assetPathProvider;
36+
37+
/// Pool for async file operations.
38+
final _pool = Pool(32);
39+
40+
IoFilesystem({required this.assetPathProvider});
41+
42+
@override
43+
Future<bool> exists(AssetId id) => _pool.withResource(_fileFor(id).exists);
44+
45+
@override
46+
Future<Uint8List> readAsBytes(AssetId id) =>
47+
_pool.withResource(_fileFor(id).readAsBytes);
48+
49+
@override
50+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
51+
_pool.withResource(_fileFor(id).readAsString);
52+
53+
@override
54+
void deleteSync(AssetId id) => _fileFor(id).deleteSync();
55+
56+
@override
57+
Future<void> delete(AssetId id) =>
58+
_pool.withResource(_fileFor(id).deleteSync);
59+
60+
@override
61+
void writeAsBytesSync(AssetId id, List<int> contents,
62+
{Encoding encoding = utf8}) {
63+
final file = _fileFor(id);
64+
file.parent.createSync(recursive: true);
65+
file.writeAsBytesSync(contents);
66+
}
67+
68+
@override
69+
Future<void> writeAsBytes(AssetId id, List<int> contents) {
70+
return _pool.withResource(() async {
71+
final file = _fileFor(id);
72+
await file.parent.create(recursive: true);
73+
await file.writeAsBytes(contents);
74+
});
75+
}
76+
77+
@override
78+
void writeAsStringSync(AssetId id, String contents,
79+
{Encoding encoding = utf8}) {
80+
final file = _fileFor(id);
81+
file.parent.createSync(recursive: true);
82+
file.writeAsStringSync(contents, encoding: encoding);
83+
}
84+
85+
@override
86+
Future<void> writeAsString(AssetId id, String contents,
87+
{Encoding encoding = utf8}) {
88+
return _pool.withResource(() async {
89+
final file = _fileFor(id);
90+
await file.parent.create(recursive: true);
91+
await file.writeAsString(contents, encoding: encoding);
92+
});
93+
}
94+
95+
/// Returns a [File] for [id] for the current [assetPathProvider].
96+
File _fileFor(AssetId id) {
97+
return File(assetPathProvider.pathFor(id));
98+
}
99+
}
100+
101+
/// An in-memory [Filesystem].
102+
class InMemoryFilesystem implements Filesystem {
103+
final Map<AssetId, List<int>> assets = {};
104+
105+
@override
106+
Future<bool> exists(AssetId id) async => assets.containsKey(id);
107+
108+
@override
109+
Future<Uint8List> readAsBytes(AssetId id) async => assets[id] as Uint8List;
110+
111+
@override
112+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async =>
113+
encoding.decode(assets[id] as Uint8List);
114+
115+
@override
116+
Future<void> delete(AssetId id) async {
117+
assets.remove(id);
118+
}
119+
120+
@override
121+
void deleteSync(AssetId id) async {
122+
assets.remove(id);
123+
}
124+
125+
@override
126+
void writeAsBytesSync(AssetId id, List<int> contents) {
127+
assets[id] = contents;
128+
}
129+
130+
@override
131+
Future<void> writeAsBytes(AssetId id, List<int> contents) async {
132+
assets[id] = contents;
133+
}
134+
135+
@override
136+
void writeAsStringSync(AssetId id, String contents,
137+
{Encoding encoding = utf8}) {
138+
assets[id] = encoding.encode(contents);
139+
}
140+
141+
@override
142+
Future<void> writeAsString(AssetId id, String contents,
143+
{Encoding encoding = utf8}) async {
144+
assets[id] = encoding.encode(contents);
145+
}
146+
}

build/lib/src/state/reader_state.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
import '../asset/reader.dart';
66
import 'asset_finder.dart';
77
import 'asset_path_provider.dart';
8+
import 'filesystem.dart';
89
import 'input_tracker.dart';
910

1011
/// Provides access to the state backing an [AssetReader].
1112
extension AssetReaderStateExtension on AssetReader {
13+
Filesystem get filesystem {
14+
_requireIsAssetReaderState();
15+
return (this as AssetReaderState).filesystem;
16+
}
17+
1218
AssetFinder get assetFinder {
1319
_requireIsAssetReaderState();
1420
return (this as AssetReaderState).assetFinder;
@@ -43,6 +49,12 @@ extension AssetReaderStateExtension on AssetReader {
4349

4450
/// The state backing an [AssetReader].
4551
abstract interface class AssetReaderState {
52+
/// The [Filesystem] that this reader reads from.
53+
///
54+
/// Warning: this access to the filesystem bypasses reader functionality
55+
/// such as read tracking, caching and visibility restriction.
56+
Filesystem get filesystem;
57+
4658
/// The [AssetFinder] associated with this reader.
4759
///
4860
/// All readers have an [AssetFinder], but the functionality it provides,

build/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dependencies:
1717
meta: ^1.3.0
1818
package_config: ^2.1.0
1919
path: ^1.8.0
20+
pool: ^1.5.0
2021

2122
dev_dependencies:
2223
build_resolvers: ^2.4.0

build/test/generate/run_post_process_builder_test.dart

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import '../common/builders.dart';
1111

1212
void main() {
1313
group('runPostProcessBuilder', () {
14-
late InMemoryAssetReader reader;
15-
late InMemoryAssetWriter writer;
14+
late InMemoryAssetReaderWriter readerWriter;
1615
final copyBuilder = CopyingPostProcessBuilder();
1716
final deleteBuilder = DeletePostProcessBuilder();
1817
final aTxt = makeAssetId('a|lib/a.txt');
@@ -24,38 +23,37 @@ void main() {
2423
void addAsset(AssetId id) => adds[id] = true;
2524
void deleteAsset(AssetId id) => deletes[id] = true;
2625

27-
Map<AssetId, String> sourceAssets;
28-
2926
setUp(() async {
30-
sourceAssets = {
31-
aTxt: 'a',
32-
};
33-
reader = InMemoryAssetReader(sourceAssets: sourceAssets);
34-
writer = InMemoryAssetWriter();
27+
readerWriter = InMemoryAssetReaderWriter()
28+
..filesystem.writeAsStringSync(aTxt, 'a');
3529
adds.clear();
3630
deletes.clear();
3731
});
3832

3933
test('can delete assets', () async {
40-
await runPostProcessBuilder(copyBuilder, aTxt, reader, writer, logger,
34+
await runPostProcessBuilder(
35+
copyBuilder, aTxt, readerWriter, readerWriter, logger,
4136
addAsset: addAsset, deleteAsset: deleteAsset);
42-
await runPostProcessBuilder(deleteBuilder, aTxt, reader, writer, logger,
37+
await runPostProcessBuilder(
38+
deleteBuilder, aTxt, readerWriter, readerWriter, logger,
4339
addAsset: addAsset, deleteAsset: deleteAsset);
4440
expect(deletes, contains(aTxt));
4541
expect(deletes, isNot(contains(aTxtCopy)));
4642
});
4743

4844
test('can create assets and read the primary asset', () async {
49-
await runPostProcessBuilder(copyBuilder, aTxt, reader, writer, logger,
45+
await runPostProcessBuilder(
46+
copyBuilder, aTxt, readerWriter, readerWriter, logger,
5047
addAsset: addAsset, deleteAsset: deleteAsset);
51-
expect(writer.assets, contains(aTxtCopy));
52-
expect(writer.assets[aTxtCopy], decodedMatches('a'));
48+
expect(readerWriter.assets, contains(aTxtCopy));
49+
expect(readerWriter.assets[aTxtCopy], decodedMatches('a'));
5350
expect(adds, contains(aTxtCopy));
5451
});
5552

5653
test('throws if addAsset throws', () async {
5754
expect(
58-
() => runPostProcessBuilder(copyBuilder, aTxt, reader, writer, logger,
55+
() => runPostProcessBuilder(
56+
copyBuilder, aTxt, readerWriter, readerWriter, logger,
5957
addAsset: (id) => throw InvalidOutputException(id, ''),
6058
deleteAsset: deleteAsset),
6159
throwsA(const TypeMatcher<InvalidOutputException>()));

build_modules/test/decoding_cache_test.dart

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,53 +39,64 @@ void main() {
3939

4040
test('can fetch from disk', () async {
4141
final id = AssetId('foo', 'lib/foo');
42-
final reader = InMemoryAssetReader(sourceAssets: {id: 'foo'});
42+
final reader = InMemoryAssetReaderWriter()
43+
..filesystem.writeAsStringSync(id, 'foo');
4344
expect(await cache.find(id, reader), 'foo');
44-
expect(reader.assetsRead, contains(id));
45+
expect(reader.inputTracker.assetsRead, contains(id));
4546
expect(fromBytesCalls, contains('foo'));
4647
});
4748

4849
test('skips read for value written this build', () async {
4950
final id = AssetId('foo', 'lib/foo');
50-
final reader = InMemoryAssetReader(sourceAssets: {id: 'foo'});
51+
final reader = InMemoryAssetReaderWriter()
52+
..filesystem.writeAsStringSync(id, 'foo');
5153
await cache.write(id, InMemoryAssetWriter(), 'bar');
5254
expect(await cache.find(id, reader), 'bar');
53-
expect(reader.assetsRead, contains(id), reason: 'Should call canRead');
55+
expect(reader.inputTracker.assetsRead, contains(id),
56+
reason: 'Should call canRead');
5457
expect(fromBytesCalls, isNot(contains('bar')));
5558
});
5659

5760
test('skips read on subsequent fetches', () async {
5861
final id = AssetId('foo', 'lib/foo');
59-
final reader1 = InMemoryAssetReader(sourceAssets: {id: 'foo'});
62+
final reader1 = InMemoryAssetReaderWriter()
63+
..filesystem.writeAsStringSync(id, 'foo');
6064
await cache.find(id, reader1);
6165
expect(fromBytesCalls['foo'], 1);
62-
final reader2 = InMemoryAssetReader(sourceAssets: {id: 'foo'});
66+
final reader2 = InMemoryAssetReaderWriter()
67+
..filesystem.writeAsStringSync(id, 'foo');
6368
expect(await cache.find(id, reader2), 'foo');
64-
expect(reader2.assetsRead, contains(id), reason: 'Should call canRead');
69+
expect(reader2.inputTracker.assetsRead, contains(id),
70+
reason: 'Should call canRead');
6571
expect(fromBytesCalls['foo'], 1, reason: 'No extra call to deserialize');
6672
});
6773

6874
test('skips read on subsequent builds if digest has not changed', () async {
6975
final id = AssetId('foo', 'lib/foo');
70-
final reader1 = InMemoryAssetReader(sourceAssets: {id: 'foo'});
76+
final reader1 = InMemoryAssetReaderWriter()
77+
..filesystem.writeAsStringSync(id, 'foo');
7178
await cache.find(id, reader1);
7279
expect(fromBytesCalls['foo'], 1);
7380
await resourceManager.disposeAll();
74-
final reader2 = InMemoryAssetReader(sourceAssets: {id: 'foo'});
81+
final reader2 = InMemoryAssetReaderWriter()
82+
..filesystem.writeAsStringSync(id, 'foo');
7583
expect(await cache.find(id, reader2), 'foo');
76-
expect(reader2.assetsRead, contains(id), reason: 'Should call canRead');
84+
expect(reader2.inputTracker.assetsRead, contains(id),
85+
reason: 'Should call canRead');
7786
expect(fromBytesCalls['foo'], 1, reason: 'No extra call to deserialize');
7887
});
7988

8089
test('rereads on subsequent builds if digest has changed', () async {
8190
final id = AssetId('foo', 'lib/foo');
82-
final reader1 = InMemoryAssetReader(sourceAssets: {id: 'foo'});
91+
final reader1 = InMemoryAssetReaderWriter()
92+
..filesystem.writeAsStringSync(id, 'foo');
8393
await cache.find(id, reader1);
8494
expect(fromBytesCalls['foo'], 1);
8595
await resourceManager.disposeAll();
86-
final reader2 = InMemoryAssetReader(sourceAssets: {id: 'bar'});
96+
final reader2 = InMemoryAssetReaderWriter()
97+
..filesystem.writeAsStringSync(id, 'bar');
8798
expect(await cache.find(id, reader2), 'bar');
88-
expect(reader2.assetsRead, contains(id));
99+
expect(reader2.inputTracker.assetsRead, contains(id));
89100
expect(fromBytesCalls['bar'], 1, reason: 'Deserialize with new value');
90101
});
91102
});

build_modules/test/meta_module_test.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ void main() {
2323
var assets = <AssetId>{};
2424
assetDescriptors.forEach((serializedId, content) {
2525
var id = AssetId.parse(serializedId);
26-
reader.cacheStringAsset(id, content);
26+
reader.filesystem.writeAsStringSync(id, content);
2727
assets.add(id);
2828
});
2929
return assets.toList();
@@ -39,7 +39,7 @@ void main() {
3939
ModuleLibrary.fromSource(s, await reader.readAsString(s)))))
4040
.where((l) => l.isImportable);
4141
for (final library in libraries) {
42-
reader.cacheStringAsset(
42+
reader.filesystem.writeAsStringSync(
4343
library.id.changeExtension(moduleLibraryExtension),
4444
library.serialize());
4545
}

0 commit comments

Comments
 (0)