Skip to content

Commit 37f5276

Browse files
authored
Adding tests for cross-module constant equality after hot restart in DDC (#2349)
Adding tests for cross-module constant equality after hot restart in DDC.
1 parent 182a6e1 commit 37f5276

File tree

12 files changed

+351
-0
lines changed

12 files changed

+351
-0
lines changed

dwds/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## 23.4.0-wip
22

3+
- Adding tests for constants in DDC after a hot restart - [#2349](https://github.com/dart-lang/webdev/pull/2349)
4+
35
## 23.3.0
46

57
- Filter out internal type properties from the new DDC type system. - [#2348](https://github.com/dart-lang/webdev/pull/2348)

dwds/test/fixtures/context.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,16 @@ class TestContext {
504504
file.writeAsStringSync(fileContents.replaceAll(toReplace, replaceWith));
505505
}
506506

507+
void makeEditToDartLibFile({
508+
required String libFileName,
509+
required String toReplace,
510+
required String replaceWith,
511+
}) {
512+
final file = File(project.dartLibFilePath(libFileName));
513+
final fileContents = file.readAsStringSync();
514+
file.writeAsStringSync(fileContents.replaceAll(toReplace, replaceWith));
515+
}
516+
507517
Future<void> waitForSuccessfulBuild({
508518
Duration? timeout,
509519
bool propagateToBrowser = false,

dwds/test/fixtures/project.dart

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,26 @@ class TestProject {
206206
nullSafety: NullSafety.sound,
207207
);
208208

209+
static const testHotRestart1 = TestProject._(
210+
packageName: '_test_hot_restart1',
211+
packageDirectory: '_testHotRestart1Sound',
212+
webAssetsPath: 'web',
213+
dartEntryFileName: 'main.dart',
214+
htmlEntryFileName: 'index.html',
215+
nullSafety: NullSafety.sound,
216+
);
217+
218+
/// This series of hot restart tests is divided across multiple packages in
219+
/// order to test correctness when only a subset of libraries are updated.
220+
static const testHotRestart2 = TestProject._(
221+
packageName: '_test_hot_restart2',
222+
packageDirectory: '_testHotRestart2Sound',
223+
webAssetsPath: 'web',
224+
dartEntryFileName: 'main.dart',
225+
htmlEntryFileName: 'index.html',
226+
nullSafety: NullSafety.sound,
227+
);
228+
209229
const TestProject._({
210230
required this.packageName,
211231
required this.packageDirectory,
@@ -234,4 +254,16 @@ class TestProject {
234254
workingDirectory: absolutePackageDirectory,
235255
);
236256
}
257+
258+
/// The path to the Dart specified file in the 'lib' directory, e.g,
259+
/// "/workstation/webdev/fixtures/_testSound/lib/library.dart":
260+
String dartLibFilePath(String dartLibFileName) => absolutePath(
261+
pathFromFixtures: p.joinAll(
262+
[
263+
packageDirectory,
264+
'lib',
265+
dartLibFileName,
266+
],
267+
),
268+
);
237269
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright (c) 2024, 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+
@Tags(['daily'])
6+
@TestOn('vm')
7+
@Timeout(Duration(minutes: 5))
8+
import 'package:dwds/dwds.dart';
9+
import 'package:test/test.dart';
10+
import 'package:test_common/logging.dart';
11+
import 'package:test_common/test_sdk_configuration.dart';
12+
import 'package:test_common/utilities.dart';
13+
import 'package:vm_service/vm_service.dart';
14+
15+
import 'fixtures/context.dart';
16+
import 'fixtures/project.dart';
17+
import 'fixtures/utilities.dart';
18+
19+
const originalString = 'variableToModifyToForceRecompile = 23';
20+
const newString = 'variableToModifyToForceRecompile = 45';
21+
22+
const constantSuccessString = 'ConstantEqualitySuccess';
23+
const constantFailureString = 'ConstantEqualityFailure';
24+
25+
void main() {
26+
// set to true for debug logging.
27+
final debug = false;
28+
29+
final provider = TestSdkConfigurationProvider(verbose: debug);
30+
tearDownAll(provider.dispose);
31+
32+
final testHotRestart2 = TestProject.testHotRestart2;
33+
final context = TestContext(testHotRestart2, provider);
34+
35+
Future<void> makeEditAndWaitForRebuild() async {
36+
context.makeEditToDartLibFile(
37+
libFileName: 'library2.dart',
38+
toReplace: originalString,
39+
replaceWith: newString,
40+
);
41+
await context.waitForSuccessfulBuild(propagateToBrowser: true);
42+
}
43+
44+
void undoEdit() {
45+
context.makeEditToDartLibFile(
46+
libFileName: 'library2.dart',
47+
toReplace: newString,
48+
replaceWith: originalString,
49+
);
50+
}
51+
52+
group(
53+
'Injected client',
54+
() {
55+
setUp(() async {
56+
setCurrentLogWriter(debug: debug);
57+
await context.setUp(
58+
testSettings: TestSettings(
59+
enableExpressionEvaluation: true,
60+
),
61+
);
62+
});
63+
64+
tearDown(() async {
65+
await context.tearDown();
66+
undoEdit();
67+
});
68+
69+
test(
70+
'properly compares constants after hot restart via the service extension',
71+
() async {
72+
final client = context.debugConnection.vmService;
73+
await client.streamListen('Isolate');
74+
75+
var source = await context.webDriver.pageSource;
76+
expect(
77+
source,
78+
contains(
79+
'ConstObject(reloadVariable: 23, ConstantEqualitySuccess)',
80+
),
81+
);
82+
83+
await makeEditAndWaitForRebuild();
84+
85+
final eventsDone = expectLater(
86+
client.onIsolateEvent,
87+
emitsThrough(
88+
emitsInOrder([
89+
_hasKind(EventKind.kIsolateExit),
90+
_hasKind(EventKind.kIsolateStart),
91+
_hasKind(EventKind.kIsolateRunnable),
92+
]),
93+
),
94+
);
95+
96+
expect(
97+
await client.callServiceExtension('hotRestart'),
98+
const TypeMatcher<Success>(),
99+
);
100+
101+
await eventsDone;
102+
103+
source = await context.webDriver.pageSource;
104+
if (dartSdkIsAtLeast('3.4.0-61.0.dev')) {
105+
expect(
106+
source,
107+
contains(
108+
'ConstObject(reloadVariable: 45, ConstantEqualitySuccess)',
109+
),
110+
);
111+
}
112+
});
113+
},
114+
timeout: Timeout.factor(2),
115+
);
116+
117+
group(
118+
'Injected client with hot restart',
119+
() {
120+
group('and with debugging', () {
121+
setUp(() async {
122+
setCurrentLogWriter(debug: debug);
123+
await context.setUp(
124+
testSettings: TestSettings(
125+
reloadConfiguration: ReloadConfiguration.hotRestart,
126+
),
127+
);
128+
});
129+
130+
tearDown(() async {
131+
await context.tearDown();
132+
undoEdit();
133+
});
134+
135+
test('properly compares constants after hot restart', () async {
136+
var source = await context.webDriver.pageSource;
137+
expect(
138+
source,
139+
contains(
140+
'ConstObject(reloadVariable: 23, ConstantEqualitySuccess)',
141+
),
142+
);
143+
144+
await makeEditAndWaitForRebuild();
145+
146+
source = await context.webDriver.pageSource;
147+
if (dartSdkIsAtLeast('3.4.0-61.0.dev')) {
148+
expect(
149+
source,
150+
contains(
151+
'ConstObject(reloadVariable: 45, ConstantEqualitySuccess)',
152+
),
153+
);
154+
}
155+
});
156+
});
157+
158+
group('and without debugging', () {
159+
setUp(() async {
160+
setCurrentLogWriter(debug: debug);
161+
await context.setUp(
162+
testSettings: TestSettings(
163+
reloadConfiguration: ReloadConfiguration.hotRestart,
164+
),
165+
debugSettings:
166+
TestDebugSettings.noDevTools().copyWith(enableDebugging: false),
167+
);
168+
});
169+
170+
tearDown(() async {
171+
await context.tearDown();
172+
undoEdit();
173+
});
174+
175+
test('properly compares constants after hot restart', () async {
176+
var source = await context.webDriver.pageSource;
177+
expect(
178+
source,
179+
contains(
180+
'ConstObject(reloadVariable: 23, ConstantEqualitySuccess)',
181+
),
182+
);
183+
184+
await makeEditAndWaitForRebuild();
185+
186+
source = await context.webDriver.pageSource;
187+
if (dartSdkIsAtLeast('3.4.0-61.0.dev')) {
188+
expect(
189+
source,
190+
contains(
191+
'ConstObject(reloadVariable: 45, ConstantEqualitySuccess)',
192+
),
193+
);
194+
}
195+
});
196+
});
197+
},
198+
timeout: Timeout.factor(2),
199+
);
200+
}
201+
202+
TypeMatcher<Event> _hasKind(String kind) =>
203+
isA<Event>().having((e) => e.kind, 'kind', kind);

dwds/test/reload_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
@Tags(['daily'])
56
@TestOn('vm')
67
@Timeout(Duration(minutes: 5))
78
import 'package:dwds/dwds.dart';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) 2024, 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+
class B {
6+
final int x;
7+
const B(this.x);
8+
}
9+
10+
B get value1 => const B(2);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
name: _test_hot_restart1
2+
version: 1.0.0
3+
description: >-
4+
A fake package used for testing hot restart.
5+
publish_to: none
6+
7+
environment:
8+
sdk: ^3.2.0-36.0.dev
9+
10+
dependencies:
11+
intl: ^0.17.0
12+
path: ^1.6.1
13+
14+
dev_dependencies:
15+
build_runner: ^2.4.0
16+
build_web_compilers: ^4.0.4
17+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) 2024, 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 'package:_test_hot_restart1/library1.dart';
6+
7+
int variableToModifyToForceRecompile = 23;
8+
B get value2 => const B(2);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: _test_hot_restart2
2+
version: 1.0.0
3+
description: >-
4+
A fake package used for testing hot restart.
5+
publish_to: none
6+
7+
environment:
8+
sdk: ^3.2.0-36.0.dev
9+
10+
dependencies:
11+
intl: ^0.17.0
12+
path: ^1.6.1
13+
_test_hot_restart1:
14+
path: ../_testHotRestart1Sound
15+
16+
dev_dependencies:
17+
build_runner: ^2.4.0
18+
build_web_compilers: ^4.0.4
19+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
3+
<head>
4+
<base href="/abc/">
5+
<script defer src="main.dart.js"></script>
6+
</head>
7+
8+
</html>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<html>
2+
3+
<head>
4+
<script defer src="main.dart.js"></script>
5+
</head>
6+
7+
</html>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2019, 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:core';
6+
import 'dart:html';
7+
8+
import 'package:_test_hot_restart1/library1.dart';
9+
import 'package:_test_hot_restart2/library2.dart';
10+
11+
/// Tests for constant semantics across hot restart in DDC.
12+
///
13+
/// DDC has multiple layers of constant caching. Failing to clear them can
14+
/// result in stale constants being referenced across hot restarts.
15+
///
16+
/// Cases tested include:
17+
/// 1) Failing to clear all constant caches.
18+
/// An old 'ConstObject' is returned, which fails to reflect the edited
19+
/// 'variableToModifyToForceRecompile'.
20+
/// 2) Clearing constant caches but failing to clear constant containers.
21+
/// Constants in reloaded modules fail to compare with constants in stale
22+
/// constant containers, causing 'ConstantEqualityFailure's.
23+
24+
class ConstObject {
25+
const ConstObject();
26+
String get text => 'ConstObject('
27+
'reloadVariable: $variableToModifyToForceRecompile, '
28+
'${value1 == value2 ? 'ConstantEqualitySuccess' : 'ConstantEqualityFailure'})';
29+
}
30+
31+
void main() {
32+
document.body!.innerHtml =
33+
'Program is running!\n${const ConstObject().text}\n';
34+
}

0 commit comments

Comments
 (0)