From 6f272eb41b76c761a619100f488c05e946af1890 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:11:33 -0800 Subject: [PATCH] Update tests --- dwds/test/reload_test.dart | 823 ++++++++++++++++++------------------- 1 file changed, 396 insertions(+), 427 deletions(-) diff --git a/dwds/test/reload_test.dart b/dwds/test/reload_test.dart index 2a72bafc3..a0bd31a5e 100644 --- a/dwds/test/reload_test.dart +++ b/dwds/test/reload_test.dart @@ -4,7 +4,7 @@ @Tags(['daily']) @TestOn('vm') -@Timeout(Duration(minutes: 5)) +@Timeout(Duration(minutes: 10)) import 'package:dwds/dwds.dart'; import 'package:test/test.dart'; @@ -44,494 +44,463 @@ void main() { ); } - group( - 'Injected client with live reload', - () { - group('and with debugging', () { - setUp(() async { - setCurrentLogWriter(debug: debug); - await context.setUp( - testSettings: TestSettings( - reloadConfiguration: ReloadConfiguration.liveReload, - ), - ); + for (final isInternalApp in [true, false]) { + group( + '${isInternalApp ? 'Internal' : 'External'} app | Injected client with live reload', + () { + group('and with debugging', () { + setUp(() async { + setCurrentLogWriter(debug: debug); + await context.setUp( + testSettings: TestSettings( + reloadConfiguration: ReloadConfiguration.liveReload, + ), + appMetadata: isInternalApp + ? TestAppMetadata.internalApp() + : TestAppMetadata.externalApp(), + ); + }); + + tearDown(() async { + undoEdit(); + await context.tearDown(); + }); + + test('can live reload changes ', () async { + await makeEditAndWaitForRebuild(); + final source = await context.webDriver.pageSource; + + _sourceIsExpected(source, isFullReload: true); + }); }); - tearDown(() async { - undoEdit(); - await context.tearDown(); + group('and without debugging', () { + setUp(() async { + setCurrentLogWriter(debug: debug); + await context.setUp( + testSettings: TestSettings( + reloadConfiguration: ReloadConfiguration.liveReload, + ), + debugSettings: TestDebugSettings.noDevTools().copyWith( + enableDebugging: false, + ), + ); + }); + + tearDown(() async { + undoEdit(); + await context.tearDown(); + }); + + test('can live reload changes ', () async { + await makeEditAndWaitForRebuild(); + + final source = await context.webDriver.pageSource; + + _sourceIsExpected(source, isFullReload: true); + }); }); - test('can live reload changes ', () async { - await makeEditAndWaitForRebuild(); - final source = await context.webDriver.pageSource; - - // A full reload should clear the state. - expect(source.contains(originalString), isFalse); - expect(source.contains(newString), isTrue); + group('and without debugging using WebSockets', () { + setUp(() async { + setCurrentLogWriter(debug: debug); + await context.setUp( + testSettings: TestSettings( + reloadConfiguration: ReloadConfiguration.liveReload, + ), + debugSettings: TestDebugSettings.noDevTools().copyWith( + enableDebugging: false, + useSse: false, + ), + ); + }); + + tearDown(() async { + await context.tearDown(); + undoEdit(); + }); + + test('can live reload changes ', () async { + await makeEditAndWaitForRebuild(); + + final source = await context.webDriver.pageSource; + + _sourceIsExpected(source, isFullReload: true); + }); }); - }); + }, + timeout: Timeout.factor(2), + ); - group('and without debugging', () { + group( + '${isInternalApp ? 'Internal' : 'External'} app | Injected client', + () { setUp(() async { setCurrentLogWriter(debug: debug); await context.setUp( testSettings: TestSettings( - reloadConfiguration: ReloadConfiguration.liveReload, - ), - debugSettings: TestDebugSettings.noDevTools().copyWith( - enableDebugging: false, + enableExpressionEvaluation: true, ), ); }); tearDown(() async { - undoEdit(); await context.tearDown(); + undoEdit(); }); - test('can live reload changes ', () async { + test('destroys and recreates the isolate during a hot restart', + () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); await makeEditAndWaitForRebuild(); - final source = await context.webDriver.pageSource; + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, + ); + + expect( + await client.callServiceExtension('hotRestart'), + const TypeMatcher(), + ); - // A full reload should clear the state. - expect(source.contains(originalString), isFalse); - expect(source.contains(newString), isTrue); + await restartComplete; }); - }); - group('and without debugging using WebSockets', () { - setUp(() async { - setCurrentLogWriter(debug: debug); - await context.setUp( - testSettings: TestSettings( - reloadConfiguration: ReloadConfiguration.liveReload, - ), - debugSettings: TestDebugSettings.noDevTools().copyWith( - enableDebugging: false, - useSse: false, + test('can execute simultaneous hot restarts', () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); + await makeEditAndWaitForRebuild(); + + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, + ); + + // Execute two hot restart calls in parallel. + final done = Future.wait([ + client.callServiceExtension('hotRestart'), + client.callServiceExtension('hotRestart'), + ]); + expect( + await done, + [const TypeMatcher(), const TypeMatcher()], + ); + + await restartComplete; + + // The debugger is still working. + final vm = await client.getVM(); + final isolateId = vm.isolates!.first.id!; + final isolate = await client.getIsolate(isolateId); + final library = isolate.rootLib!.uri!; + + final result = await client.evaluate(isolateId, library, 'true'); + expect( + result, + isA().having( + (instance) => instance.valueAsString, + 'valueAsString', + 'true', ), ); }); - tearDown(() async { - await context.tearDown(); - undoEdit(); + test('destroys and recreates the isolate during a page refresh', + () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); + await makeEditAndWaitForRebuild(); + + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: true, + ); + + await context.webDriver.driver.refresh(); + + await restartComplete; }); - test('can live reload changes ', () async { + test('can hot restart via the service extension', () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); await makeEditAndWaitForRebuild(); - final source = await context.webDriver.pageSource; + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, + ); - // A full reload should clear the state. - expect(source.contains(originalString), isFalse); - expect(source.contains(newString), isTrue); + expect( + await client.callServiceExtension('hotRestart'), + const TypeMatcher(), + ); + print('waiting for restart complete'); + await restartComplete; + print('done waiting for restart complete'); + + final source = await context.webDriver.pageSource; + _sourceIsExpected(source, isFullReload: !isInternalApp); }); - }); - }, - timeout: Timeout.factor(2), - ); - - group( - 'Injected client', - () { - setUp(() async { - setCurrentLogWriter(debug: debug); - await context.setUp( - testSettings: TestSettings( - enableExpressionEvaluation: true, - ), - ); - }); - - tearDown(() async { - await context.tearDown(); - undoEdit(); - }); - - test('destroys and recreates the isolate during a hot restart', () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - await makeEditAndWaitForRebuild(); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - expect( - await client.callServiceExtension('hotRestart'), - const TypeMatcher(), - ); - - await restartComplete; - }); - - test('can execute simultaneous hot restarts', () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - await makeEditAndWaitForRebuild(); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - // Execute two hot restart calls in parallel. - final done = Future.wait([ - client.callServiceExtension('hotRestart'), - client.callServiceExtension('hotRestart'), - ]); - expect( - await done, - [const TypeMatcher(), const TypeMatcher()], - ); - - await restartComplete; - - // The debugger is still working. - final vm = await client.getVM(); - final isolateId = vm.isolates!.first.id!; - final isolate = await client.getIsolate(isolateId); - final library = isolate.rootLib!.uri!; - - final result = await client.evaluate(isolateId, library, 'true'); - expect( - result, - isA().having( - (instance) => instance.valueAsString, - 'valueAsString', - 'true', - ), - ); - }); - - test('destroys and recreates the isolate during a page refresh', - () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - await makeEditAndWaitForRebuild(); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - await context.webDriver.driver.refresh(); - - await restartComplete; - }); - - test('can hot restart via the service extension', () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - await makeEditAndWaitForRebuild(); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - expect( - await client.callServiceExtension('hotRestart'), - const TypeMatcher(), - ); - - await restartComplete; - - final source = await context.webDriver.pageSource; - // Hot-restart triggers a full reload which clears the state. - expect(source.contains(originalString), isFalse); - expect(source, contains(newString)); - }); - - test('can send events before and after hot restart', () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - - // The event just before hot restart might never be received, - // but the injected client continues to work and send events - // after hot restart. - final eventReceived = expectLater( - client.onIsolateEvent, - emitsThrough( - _hasKind(EventKind.kServiceExtensionAdded) - .having((e) => e.extensionRPC, 'service', 'ext.bar'), - ), - ); - - var vm = await client.getVM(); - var isolateId = vm.isolates!.first.id!; - var isolate = await client.getIsolate(isolateId); - var library = isolate.rootLib!.uri!; - - final String callback = - '(_, __) async => ServiceExtensionResponse.result("")'; - - await client.evaluate( - isolateId, - library, - "registerExtension('ext.foo', $callback)", - ); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - expect( - await client.callServiceExtension('hotRestart'), - const TypeMatcher(), - ); - - await restartComplete; - - vm = await client.getVM(); - isolateId = vm.isolates!.first.id!; - isolate = await client.getIsolate(isolateId); - library = isolate.rootLib!.uri!; - - await client.evaluate( - isolateId, - library, - "registerExtension('ext.bar', $callback)", - ); - - await eventReceived; - - final source = await context.webDriver.pageSource; - // Main is re-invoked which shouldn't clear the state. - expect(source, contains('Hello World!')); - }); - - test('can refresh the page via the fullReload service extension', - () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - await makeEditAndWaitForRebuild(); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - expect(await client.callServiceExtension('fullReload'), isA()); - - await restartComplete; - - final source = await context.webDriver.pageSource; - // Should see only the new text - expect(source.contains(originalString), isFalse); - expect(source.contains(newString), isTrue); - }); - - test('can hot restart while paused', () async { - final client = context.debugConnection.vmService; - var vm = await client.getVM(); - var isolateId = vm.isolates!.first.id!; - await client.streamListen('Isolate'); - await client.streamListen('Debug'); - final stream = client.onEvent('Debug'); - final scriptList = await client.getScripts(isolateId); - final main = scriptList.scripts! - .firstWhere((script) => script.uri!.contains('main.dart')); - final bpLine = - await context.findBreakpointLine('printCount', isolateId, main); - await client.addBreakpoint(isolateId, main.id!, bpLine); - await stream - .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint); - - await makeEditAndWaitForRebuild(); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - expect( - await client.callServiceExtension('hotRestart'), - const TypeMatcher(), - ); - - await restartComplete; - - final source = await context.webDriver.pageSource; - - // Hot-restart triggers a full reload which clears the state. - expect(source.contains(originalString), isFalse); - expect(source, contains(newString)); - - vm = await client.getVM(); - isolateId = vm.isolates!.first.id!; - final isolate = await client.getIsolate(isolateId); - - // Previous breakpoint should be cleared. - expect(isolate.breakpoints!.isEmpty, isTrue); - }); - - test('can evaluate expressions after hot restart', () async { - final client = context.debugConnection.vmService; - await client.streamListen('Isolate'); - - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), - ); - - await client.callServiceExtension('hotRestart'); - - await restartComplete; - - final vm = await client.getVM(); - final isolateId = vm.isolates!.first.id!; - final isolate = await client.getIsolate(isolateId); - final library = isolate.rootLib!.uri!; - - // Expression evaluation while running should work. - final result = await client.evaluate(isolateId, library, 'true'); - expect( - result, - isA().having( - (instance) => instance.valueAsString, - 'valueAsString', - 'true', - ), - ); - }); - }, - timeout: Timeout.factor(2), - ); - - group( - 'Injected client with hot restart', - () { - group('and with debugging', () { - setUp(() async { - setCurrentLogWriter(debug: debug); - await context.setUp( - testSettings: TestSettings( - reloadConfiguration: ReloadConfiguration.hotRestart, + + test('can send events before and after hot restart', () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); + + // The event just before hot restart might never be received, + // but the injected client continues to work and send events + // after hot restart. + final eventReceived = expectLater( + client.onIsolateEvent, + emitsThrough( + _hasKind(EventKind.kServiceExtensionAdded) + .having((e) => e.extensionRPC, 'service', 'ext.bar'), ), ); + + var vm = await client.getVM(); + var isolateId = vm.isolates!.first.id!; + var isolate = await client.getIsolate(isolateId); + var library = isolate.rootLib!.uri!; + + final String callback = + '(_, __) async => ServiceExtensionResponse.result("")'; + + await client.evaluate( + isolateId, + library, + "registerExtension('ext.foo', $callback)", + ); + + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, + ); + + expect( + await client.callServiceExtension('hotRestart'), + const TypeMatcher(), + ); + + await restartComplete; + + vm = await client.getVM(); + isolateId = vm.isolates!.first.id!; + isolate = await client.getIsolate(isolateId); + library = isolate.rootLib!.uri!; + + await client.evaluate( + isolateId, + library, + "registerExtension('ext.bar', $callback)", + ); + + await eventReceived; + + final source = await context.webDriver.pageSource; + _sourceIsExpected(source, isFullReload: !isInternalApp); }); - tearDown(() async { - await context.tearDown(); - undoEdit(); + test('can refresh the page via the fullReload service extension', + () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); + await makeEditAndWaitForRebuild(); + + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: true, + ); + + expect( + await client.callServiceExtension('fullReload'), isA()); + + await restartComplete; + + final source = await context.webDriver.pageSource; + _sourceIsExpected(source, isFullReload: true); }); - test('can hot restart changes ', () async { + test('can hot restart while paused', () async { + final client = context.debugConnection.vmService; + var vm = await client.getVM(); + var isolateId = vm.isolates!.first.id!; + await client.streamListen('Isolate'); + await client.streamListen('Debug'); + final stream = client.onEvent('Debug'); + final scriptList = await client.getScripts(isolateId); + final main = scriptList.scripts! + .firstWhere((script) => script.uri!.contains('main.dart')); + final bpLine = + await context.findBreakpointLine('printCount', isolateId, main); + await client.addBreakpoint(isolateId, main.id!, bpLine); + await stream + .firstWhere((event) => event.kind == EventKind.kPauseBreakpoint); + await makeEditAndWaitForRebuild(); + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, + ); + + expect( + await client.callServiceExtension('hotRestart'), + const TypeMatcher(), + ); + + await restartComplete; + final source = await context.webDriver.pageSource; // Hot-restart triggers a full reload which clears the state. - expect(source.contains(originalString), isFalse); - expect(source.contains(newString), isTrue); + _sourceIsExpected(source, isFullReload: !isInternalApp); + + vm = await client.getVM(); + isolateId = vm.isolates!.first.id!; + final isolate = await client.getIsolate(isolateId); + + // Previous breakpoint should be cleared. + expect(isolate.breakpoints!.isEmpty, isTrue); }); - test('fires isolate create/destroy events during hot restart', - () async { + test('can evaluate expressions after hot restart', () async { final client = context.debugConnection.vmService; await client.streamListen('Isolate'); - final restartComplete = expectLater( - client.onIsolateEvent, - emitsThrough( - emitsInOrder([ - _hasKind(EventKind.kIsolateExit), - _hasKind(EventKind.kIsolateStart), - _hasKind(EventKind.kIsolateRunnable), - ]), - ), + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, ); - await makeEditAndWaitForRebuild(); + await client.callServiceExtension('hotRestart'); await restartComplete; - }); - }); - group('and without debugging', () { - setUp(() async { - setCurrentLogWriter(debug: debug); - await context.setUp( - testSettings: TestSettings( - reloadConfiguration: ReloadConfiguration.hotRestart, + final vm = await client.getVM(); + final isolateId = vm.isolates!.first.id!; + final isolate = await client.getIsolate(isolateId); + final library = isolate.rootLib!.uri!; + + // Expression evaluation while running should work. + final result = await client.evaluate(isolateId, library, 'true'); + expect( + result, + isA().having( + (instance) => instance.valueAsString, + 'valueAsString', + 'true', ), - debugSettings: - TestDebugSettings.noDevTools().copyWith(enableDebugging: false), ); }); + }, + timeout: Timeout.factor(2), + ); - tearDown(() async { - await context.tearDown(); - undoEdit(); + group( + '${isInternalApp ? 'Internal' : 'External'} app | Injected client with hot restart', + () { + group('and with debugging', () { + setUp(() async { + setCurrentLogWriter(debug: debug); + await context.setUp( + testSettings: TestSettings( + reloadConfiguration: ReloadConfiguration.hotRestart, + ), + ); + }); + + tearDown(() async { + await context.tearDown(); + undoEdit(); + }); + + test('can hot restart changes ', () async { + await makeEditAndWaitForRebuild(); + + final source = await context.webDriver.pageSource; + + _sourceIsExpected(source, isFullReload: !isInternalApp); + }); + + test('fires isolate create/destroy events during hot restart', + () async { + final client = context.debugConnection.vmService; + await client.streamListen('Isolate'); + + final restartComplete = _restartCompleteExpectation( + isolateEventStream: client.onIsolateEvent, + isFullReload: !isInternalApp, + ); + + await makeEditAndWaitForRebuild(); + + await restartComplete; + }); }); - test('can hot restart changes ', () async { - await makeEditAndWaitForRebuild(); + group('and without debugging', () { + setUp(() async { + setCurrentLogWriter(debug: debug); + await context.setUp( + testSettings: TestSettings( + reloadConfiguration: ReloadConfiguration.hotRestart, + ), + debugSettings: TestDebugSettings.noDevTools() + .copyWith(enableDebugging: false), + ); + }); + + tearDown(() async { + await context.tearDown(); + undoEdit(); + }); + + test('can hot restart changes ', () async { + await makeEditAndWaitForRebuild(); + + final source = await context.webDriver.pageSource; + + _sourceIsExpected(source, isFullReload: !isInternalApp); + }); + }); + }, + timeout: Timeout.factor(2), + ); + } +} - final source = await context.webDriver.pageSource; +Future _restartCompleteExpectation({ + required Stream isolateEventStream, + required bool isFullReload, +}) { + if (isFullReload) { + return expectLater( + isolateEventStream, + emitsThrough( + emitsInOrder([ + _hasKind(EventKind.kIsolateExit), + _hasKind(EventKind.kIsolateStart), + _hasKind(EventKind.kIsolateRunnable), + ]), + ), + ); + } - // Hot-restart triggers a full reload which clears the state. - expect(source.contains(originalString), isFalse); - expect(source.contains(newString), isTrue); - }); - }); - }, - timeout: Timeout.factor(2), - ); + return Future.value(null); +} + +void _sourceIsExpected(String source, {required bool isFullReload}) { + expect(source, contains(newString)); + if (isFullReload) { + // A full reload clears the state. + expect(source.contains(originalString), isFalse); + } else { + // A hot-restart invokes main which shouldn't clear the state. + expect(source, contains(originalString)); + } } TypeMatcher _hasKind(String kind) =>