Skip to content

Commit 55c36f0

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

35 files changed

+406
-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/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Use `build_test` 3.0.0.
66
- Refactor `PathProvidingAssetReader` to `AssetPathProvider`.
77
- Refactor `MultiPackageAssetReader` to internal `AssetFinder`.
8+
- Add internal `Filesystem` that backs `AssetReader` and `AssetWriter`
9+
implementations.
810

911
## 2.4.2
1012

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: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
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+
///
16+
/// Methods behave as the `dart:io` methods with the same names, with some
17+
/// exceptions noted.
18+
abstract interface class Filesystem {
19+
Future<bool> exists(AssetId id);
20+
21+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8});
22+
Future<Uint8List> readAsBytes(AssetId id);
23+
24+
/// Deletes a file.
25+
///
26+
/// If the file does not exist, does nothing.
27+
Future<void> delete(AssetId id);
28+
29+
/// Deletes a file.
30+
///
31+
/// If the file does not exist, does nothing.
32+
void deleteSync(AssetId id);
33+
34+
/// Writes a file.
35+
///
36+
/// Creates enclosing directories as needed if they don't exist.
37+
void writeAsStringSync(AssetId id, String contents,
38+
{Encoding encoding = utf8});
39+
40+
/// Writes a file.
41+
///
42+
/// Creates enclosing directories as needed if they don't exist.
43+
Future<void> writeAsString(AssetId id, String contents,
44+
{Encoding encoding = utf8});
45+
46+
/// Writes a file.
47+
///
48+
/// Creates enclosing directories as needed if they don't exist.
49+
void writeAsBytesSync(AssetId id, List<int> contents);
50+
51+
/// Writes a file.
52+
///
53+
/// Creates enclosing directories as needed if they don't exist.
54+
Future<void> writeAsBytes(AssetId id, List<int> contents);
55+
}
56+
57+
/// A filesystem using [assetPathProvider] to map to the `dart:io` filesystem.
58+
class IoFilesystem implements Filesystem {
59+
final AssetPathProvider assetPathProvider;
60+
61+
/// Pool for async file operations.
62+
final _pool = Pool(32);
63+
64+
IoFilesystem({required this.assetPathProvider});
65+
66+
@override
67+
Future<bool> exists(AssetId id) => _pool.withResource(_fileFor(id).exists);
68+
69+
@override
70+
Future<Uint8List> readAsBytes(AssetId id) =>
71+
_pool.withResource(_fileFor(id).readAsBytes);
72+
73+
@override
74+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) =>
75+
_pool.withResource(_fileFor(id).readAsString);
76+
77+
@override
78+
void deleteSync(AssetId id) {
79+
final file = _fileFor(id);
80+
if (file.existsSync()) file.deleteSync();
81+
}
82+
83+
@override
84+
Future<void> delete(AssetId id) {
85+
return _pool.withResource(() async {
86+
final file = _fileFor(id);
87+
if (await file.exists()) await file.delete();
88+
});
89+
}
90+
91+
@override
92+
void writeAsBytesSync(AssetId id, List<int> contents,
93+
{Encoding encoding = utf8}) {
94+
final file = _fileFor(id);
95+
file.parent.createSync(recursive: true);
96+
file.writeAsBytesSync(contents);
97+
}
98+
99+
@override
100+
Future<void> writeAsBytes(AssetId id, List<int> contents) {
101+
return _pool.withResource(() async {
102+
final file = _fileFor(id);
103+
await file.parent.create(recursive: true);
104+
await file.writeAsBytes(contents);
105+
});
106+
}
107+
108+
@override
109+
void writeAsStringSync(AssetId id, String contents,
110+
{Encoding encoding = utf8}) {
111+
final file = _fileFor(id);
112+
file.parent.createSync(recursive: true);
113+
file.writeAsStringSync(contents, encoding: encoding);
114+
}
115+
116+
@override
117+
Future<void> writeAsString(AssetId id, String contents,
118+
{Encoding encoding = utf8}) {
119+
return _pool.withResource(() async {
120+
final file = _fileFor(id);
121+
await file.parent.create(recursive: true);
122+
await file.writeAsString(contents, encoding: encoding);
123+
});
124+
}
125+
126+
/// Returns a [File] for [id] for the current [assetPathProvider].
127+
File _fileFor(AssetId id) {
128+
return File(assetPathProvider.pathFor(id));
129+
}
130+
}
131+
132+
/// An in-memory [Filesystem].
133+
class InMemoryFilesystem implements Filesystem {
134+
final Map<AssetId, List<int>> assets = {};
135+
136+
@override
137+
Future<bool> exists(AssetId id) async => assets.containsKey(id);
138+
139+
@override
140+
Future<Uint8List> readAsBytes(AssetId id) async => assets[id] as Uint8List;
141+
142+
@override
143+
Future<String> readAsString(AssetId id, {Encoding encoding = utf8}) async =>
144+
encoding.decode(assets[id] as Uint8List);
145+
146+
@override
147+
Future<void> delete(AssetId id) async {
148+
assets.remove(id);
149+
}
150+
151+
@override
152+
void deleteSync(AssetId id) async {
153+
assets.remove(id);
154+
}
155+
156+
@override
157+
void writeAsBytesSync(AssetId id, List<int> contents) {
158+
assets[id] = contents;
159+
}
160+
161+
@override
162+
Future<void> writeAsBytes(AssetId id, List<int> contents) async {
163+
assets[id] = contents;
164+
}
165+
166+
@override
167+
void writeAsStringSync(AssetId id, String contents,
168+
{Encoding encoding = utf8}) {
169+
assets[id] = encoding.encode(contents);
170+
}
171+
172+
@override
173+
Future<void> writeAsString(AssetId id, String contents,
174+
{Encoding encoding = utf8}) async {
175+
assets[id] = encoding.encode(contents);
176+
}
177+
}

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>()));

0 commit comments

Comments
 (0)