Skip to content

Commit 6e046f9

Browse files
Ilya Yanokcopybara-github
authored andcommitted
Don't try to fake unfakable classes
We have to rely on runtime dummy value lookup instead. Also adds a dummy value for SDK's `ProcessResult`, `SplayTreeMap`, `SplayTreeSet` and `Isolate` classes, which are `final`. I guess we want to cover all final SDK classes eventually. Also bump the version used in the generator tests to 3.0. #dart3 PiperOrigin-RevId: 534338463
1 parent 6e778ea commit 6e046f9

File tree

6 files changed

+127
-30
lines changed

6 files changed

+127
-30
lines changed

lib/src/builder.dart

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,7 @@ class MockBuilder implements Builder {
124124
// The source lib may be pre-null-safety because of an explicit opt-out
125125
// (`// @dart=2.9`), as opposed to living in a pre-null-safety package. To
126126
// allow for this situation, we must also add an opt-out comment here.
127-
final dartVersionComment =
128-
sourceLibIsNonNullable ? '// @dart=2.19' : '// @dart=2.9';
127+
final dartVersionComment = sourceLibIsNonNullable ? '' : '// @dart=2.9';
129128
final mockLibraryContent = DartFormatter().format('''
130129
// Mocks generated by Mockito $packageVersion from annotations
131130
// in ${entryLib.definingCompilationUnit.source.uri.path}.
@@ -1444,6 +1443,11 @@ class _MockClassInfo {
14441443
]);
14451444
}
14461445

1446+
Expression _dummyValueFallbackToRuntime(
1447+
analyzer.DartType type, Expression invocation) =>
1448+
referImported('dummyValue', 'package:mockito/src/dummies.dart')
1449+
.call([refer('this'), invocation], {}, [_typeReference(type)]);
1450+
14471451
Expression _dummyValue(analyzer.DartType type, Expression invocation) {
14481452
// The type is nullable, just take a shortcut and return `null`.
14491453
if (typeSystem.isNullable(type)) {
@@ -1460,8 +1464,7 @@ class _MockClassInfo {
14601464
return literalNull;
14611465
}
14621466
// As a last resort, try looking for the correct value at run-time.
1463-
return referImported('dummyValue', 'package:mockito/src/dummies.dart')
1464-
.call([refer('this'), invocation], {}, [_typeReference(type)]);
1467+
return _dummyValueFallbackToRuntime(type, invocation);
14651468
}
14661469

14671470
final typeArguments = type.typeArguments;
@@ -1599,24 +1602,47 @@ class _MockClassInfo {
15991602
}).genericClosure;
16001603
}
16011604

1605+
Expression _dummyFakedValue(
1606+
analyzer.InterfaceType dartType, Expression invocation) {
1607+
final elementToFake = dartType.element;
1608+
final fakeName = mockLibraryInfo._fakeNameFor(elementToFake);
1609+
// Only make one fake class for each class that needs to be faked.
1610+
if (!mockLibraryInfo.fakedInterfaceElements.contains(elementToFake)) {
1611+
_addFakeClass(fakeName, elementToFake);
1612+
}
1613+
final typeArguments = dartType.typeArguments;
1614+
return TypeReference((b) {
1615+
b
1616+
..symbol = fakeName
1617+
..types.addAll(typeArguments.map(_typeReference));
1618+
}).newInstance([refer('this'), invocation]);
1619+
}
1620+
16021621
Expression _dummyValueImplementing(
16031622
analyzer.InterfaceType dartType, Expression invocation) {
16041623
final elementToFake = dartType.element;
16051624
if (elementToFake is EnumElement) {
16061625
return _typeReference(dartType).property(
16071626
elementToFake.fields.firstWhere((f) => f.isEnumConstant).name);
1627+
} else if (elementToFake is ClassElement) {
1628+
if (elementToFake.isBase ||
1629+
elementToFake.isFinal ||
1630+
elementToFake.isSealed) {
1631+
// This class can't be faked, so try to call `dummyValue` to get
1632+
// a dummy value at run time.
1633+
// TODO(yanok): Consider checking subtypes, maybe some of them are
1634+
// implementable.
1635+
return _dummyValueFallbackToRuntime(dartType, invocation);
1636+
}
1637+
return _dummyFakedValue(dartType, invocation);
1638+
} else if (elementToFake is MixinElement) {
1639+
// This is a mixin and not a class. This should not happen in Dart 3,
1640+
// since it is not possible to have a value of mixin type. But we
1641+
// have to support this for reverse comptatibility.
1642+
return _dummyFakedValue(dartType, invocation);
16081643
} else {
1609-
final fakeName = mockLibraryInfo._fakeNameFor(elementToFake);
1610-
// Only make one fake class for each class that needs to be faked.
1611-
if (!mockLibraryInfo.fakedInterfaceElements.contains(elementToFake)) {
1612-
_addFakeClass(fakeName, elementToFake);
1613-
}
1614-
final typeArguments = dartType.typeArguments;
1615-
return TypeReference((b) {
1616-
b
1617-
..symbol = fakeName
1618-
..types.addAll(typeArguments.map(_typeReference));
1619-
}).newInstance([refer('this'), invocation]);
1644+
throw StateError("Interface type '$dartType' which is nether an enum, "
1645+
'nor a class, nor a mixin. This case is unknown, please report a bug.');
16201646
}
16211647
}
16221648

lib/src/dummies.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
// limitations under the License.
1414

1515
import 'dart:async';
16+
import 'dart:collection';
1617
import 'dart:typed_data';
1718
import 'mock.dart' show FakeFunctionUsedError;
19+
import 'platform_dummies_js.dart'
20+
if (dart.library.io) 'platform_dummies_vm.dart';
1821

1922
// TODO(yanok): try to change these to _unreasonable_ values, for example,
2023
// String could totally contain an explanation.
@@ -111,6 +114,7 @@ Map<Type, DummyBuilder> _defaultDummyBuilders = {
111114
Float32List: (_, _i) => Float32List(0),
112115
Float64List: (_, _i) => Float64List(0),
113116
ByteData: (_, _i) => ByteData(0),
117+
...platformDummies,
114118
};
115119

116120
List<Object?> _defaultDummies = [
@@ -123,6 +127,8 @@ List<Object?> _defaultDummies = [
123127
<Never>{},
124128
<Never, Never>{},
125129
Stream<Never>.empty(),
130+
SplayTreeSet<Never>(),
131+
SplayTreeMap<Never, Never>(),
126132
];
127133

128134
T? dummyValueOrNull<T>(Object parent, Invocation invocation) {

lib/src/platform_dummies_js.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 Dart Mockito authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'dummies.dart' show DummyBuilder;
16+
17+
Map<Type, DummyBuilder> platformDummies = {};

lib/src/platform_dummies_vm.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright 2023 Dart Mockito authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import 'dart:io';
16+
import 'dart:isolate';
17+
import 'dummies.dart' show DummyBuilder;
18+
import 'mock.dart' show SmartFake;
19+
20+
class FakeSendPort extends SmartFake implements SendPort {
21+
FakeSendPort(super.parent, super.invocation);
22+
}
23+
24+
Map<Type, DummyBuilder> platformDummies = {
25+
ProcessResult: (parent, invocation) => ProcessResult(0, 0, '', '''
26+
dummy ProcessResult created for a call to $parent.${invocation.memberName}'''),
27+
// We can't fake `Isolate`, but we can fake `SendPort`, so use it.
28+
Isolate: (parent, invocation) => Isolate(FakeSendPort(parent, invocation)),
29+
};

test/builder/auto_mocks_test.dart

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,10 @@ void main() {
102102
final packageConfig = PackageConfig([
103103
Package('foo', Uri.file('/foo/'),
104104
packageUriRoot: Uri.file('/foo/lib/'),
105-
languageVersion: LanguageVersion(2, 13))
105+
languageVersion: LanguageVersion(3, 0))
106106
]);
107-
await withEnabledExperiments(
108-
() async => await testBuilder(
109-
buildMocks(BuilderOptions({})), sourceAssets,
110-
writer: writer, outputs: outputs, packageConfig: packageConfig),
111-
['nonfunction-type-aliases'],
112-
);
107+
await testBuilder(buildMocks(BuilderOptions({})), sourceAssets,
108+
writer: writer, outputs: outputs, packageConfig: packageConfig);
113109
}
114110

115111
/// Builds with [MockBuilder] in a package which has opted into null safety,
@@ -118,15 +114,11 @@ void main() {
118114
final packageConfig = PackageConfig([
119115
Package('foo', Uri.file('/foo/'),
120116
packageUriRoot: Uri.file('/foo/lib/'),
121-
languageVersion: LanguageVersion(2, 13))
117+
languageVersion: LanguageVersion(3, 0))
122118
]);
123119

124-
await withEnabledExperiments(
125-
() async => await testBuilder(
126-
buildMocks(BuilderOptions({})), sourceAssets,
127-
writer: writer, packageConfig: packageConfig),
128-
['nonfunction-type-aliases'],
129-
);
120+
await testBuilder(buildMocks(BuilderOptions({})), sourceAssets,
121+
writer: writer, packageConfig: packageConfig);
130122
final mocksAsset = AssetId('foo', 'test/foo_test.mocks.dart');
131123
return utf8.decode(writer.assets[mocksAsset]!);
132124
}
@@ -2730,6 +2722,33 @@ void main() {
27302722
hasLength(1));
27312723
});
27322724

2725+
test('does not try to generate a fake for a final class', () async {
2726+
await expectSingleNonNullableOutput(dedent(r'''
2727+
class Foo {
2728+
Bar m1() => Bar();
2729+
}
2730+
final class Bar {}
2731+
'''), _containsAllOf('dummyValue<_i2.Bar>('));
2732+
});
2733+
2734+
test('does not try to generate a fake for a base class', () async {
2735+
await expectSingleNonNullableOutput(dedent(r'''
2736+
class Foo {
2737+
Bar m1() => Bar();
2738+
}
2739+
base class Bar {}
2740+
'''), _containsAllOf('dummyValue<_i2.Bar>('));
2741+
});
2742+
2743+
test('does not try to generate a fake for a sealed class', () async {
2744+
await expectSingleNonNullableOutput(dedent(r'''
2745+
class Foo {
2746+
Bar m1() => Bar();
2747+
}
2748+
sealed class Bar {}
2749+
'''), _containsAllOf('dummyValue<_i2.Bar>('));
2750+
});
2751+
27332752
test('throws when GenerateMocks is given a class multiple times', () async {
27342753
_expectBuilderThrows(
27352754
assets: {

test/builder/custom_mocks_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ void main() {
110110
final packageConfig = PackageConfig([
111111
Package('foo', Uri.file('/foo/'),
112112
packageUriRoot: Uri.file('/foo/lib/'),
113-
languageVersion: LanguageVersion(2, 15))
113+
languageVersion: LanguageVersion(3, 0))
114114
]);
115115
await testBuilder(buildMocks(BuilderOptions({})), sourceAssets,
116116
writer: writer, packageConfig: packageConfig);

0 commit comments

Comments
 (0)