| // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:developer' show debugger; |
| import 'dart:io' show Directory, File; |
| import 'dart:isolate' as i; |
| |
| import 'package:path/path.dart' show join; |
| import 'package:test/test.dart'; |
| import 'package:vm_service/vm_service.dart'; |
| |
| import 'common/service_test_common.dart'; |
| import 'common/test_helper.dart'; |
| |
| // AUTOGENERATED START |
| // |
| // Update these constants by running: |
| // |
| // dart pkg/vm_service/test/update_line_numbers.dart <test.dart> |
| // |
| const LINE_A = 78; |
| // AUTOGENERATED END |
| |
| const v0Contents = ''' |
| import 'dart:developer'; |
| |
| void f() {} |
| |
| void main() { |
| debugger(); |
| f(); |
| f(); |
| } |
| '''; |
| |
| const v1Contents = ''' |
| import 'dart:developer'; |
| |
| void f() { |
| (() { |
| (() { |
| print('v1'); |
| })(); |
| })(); |
| } |
| |
| void main() { |
| f(); |
| f(); |
| } |
| '''; |
| |
| const v2Contents = ''' |
| import 'dart:developer'; |
| |
| void f() { |
| (() { |
| print('v2.a'); |
| print('v2.b'); |
| })(); |
| } |
| |
| void main() { |
| f(); |
| f(); |
| } |
| '''; |
| |
| Future<void> testeeMain() async { |
| // Spawn the child isolate. |
| final tempDir = Directory.systemTemp.createTempSync(); |
| try { |
| final rootLib = File(join(tempDir.path, 'main.dart')); |
| rootLib.writeAsStringSync(v0Contents); |
| |
| await i.Isolate.spawnUri(rootLib.uri, [], null); |
| debugger(); // LINE_A |
| tempDir.deleteSync(recursive: true); |
| } catch (_) { |
| tempDir.deleteSync(recursive: true); |
| rethrow; |
| } |
| } |
| |
| final tests = <IsolateTest>[ |
| // Ensure that the main isolate has stopped at the [debugger] statement at the |
| // end of [testeeMain]. |
| hasStoppedAtBreakpoint, |
| stoppedAtLine(LINE_A), |
| (VmService service, IsolateRef isolateRef) async { |
| final tempDir = Directory.systemTemp.createTempSync(); |
| try { |
| // This test is a regression test against a bug caused by comparing script |
| // URLs instead of script pointers in the debugger. To produce a situation |
| // in which the bug used to occur, this test loads |
| // [spawnedIsolateRootLib], modifies [spawnedIsolateRootLib], and then |
| // reloads [spawnedIsolateRootLib]. |
| final spawnedIsolateRootLib = File(join(tempDir.path, 'main.dart')); |
| |
| // Find the spawned isolate. |
| final vm = await service.getVM(); |
| final isolates = vm.isolates!; |
| expect(isolates.length, 2); |
| final spawnedIsolateRef = isolates.firstWhere( |
| (i) => i != isolateRef, |
| ); |
| final spawnedIsolateId = spawnedIsolateRef.id!; |
| |
| // Load [v1Contents] into the spawned isolate. |
| spawnedIsolateRootLib.writeAsStringSync(v1Contents); |
| await service.reloadSources( |
| spawnedIsolateId, |
| rootLibUri: spawnedIsolateRootLib.uri.toString(), |
| force: true, |
| ); |
| |
| Isolate spawnedIsolate = await service.getIsolate(spawnedIsolateId); |
| Library rootLib = await service.getObject( |
| spawnedIsolateId, |
| spawnedIsolate.rootLib!.id!, |
| ) as Library; |
| String scriptId = rootLib.scripts![0].id!; |
| |
| // Add a breakpoint at `print('v1');`. |
| await service.addBreakpoint(spawnedIsolateId, scriptId, 6); |
| |
| // Resuming the spawned isolate should let it run until it gets paused at |
| // the breakpoint at `print('v1');`. |
| await resumeIsolate(service, spawnedIsolateRef); |
| await hasStoppedAtBreakpoint(service, spawnedIsolateRef); |
| await stoppedAtLine(6)(service, spawnedIsolateRef); |
| |
| // Load [v2Contents] into the spawned isolate. |
| spawnedIsolateRootLib.writeAsStringSync(v2Contents); |
| await service.reloadSources( |
| spawnedIsolateId, |
| rootLibUri: spawnedIsolateRootLib.uri.toString(), |
| force: true, |
| ); |
| |
| spawnedIsolate = await service.getIsolate(spawnedIsolateId); |
| rootLib = await service.getObject( |
| spawnedIsolateId, |
| spawnedIsolate.rootLib!.id!, |
| ) as Library; |
| scriptId = rootLib.scripts![0].id!; |
| |
| // Add a breakpoint at `print('v2.a');`. |
| await service.addBreakpoint(spawnedIsolateId, scriptId, 5); |
| |
| // Resuming the spawned isolate should let it run until it gets paused at |
| // the breakpoint at `print('v2.a');`. |
| await resumeIsolate(service, spawnedIsolateRef); |
| await hasStoppedAtBreakpoint(service, spawnedIsolateRef); |
| await stoppedAtLine(5)(service, spawnedIsolateRef); |
| |
| // Add a breakpoint at `print('v2.b');`. |
| final breakpoint3 = |
| await service.addBreakpoint(spawnedIsolateId, scriptId, 6); |
| expect(breakpoint3.breakpointNumber, 3); |
| |
| // We previously had a bug that would have made the breakpoint resolution |
| // code get confused by the old closure that was defined in [v1Contents]. |
| // We prevent a reintroduction of that bug by ensuring that the newly set |
| // breakpoint has been resolved immediately. |
| expect(breakpoint3.resolved, true); |
| |
| await resumeIsolate(service, spawnedIsolateRef); |
| tempDir.deleteSync(recursive: true); |
| } catch (_) { |
| tempDir.deleteSync(recursive: true); |
| rethrow; |
| } |
| }, |
| ]; |
| |
| void main([args = const <String>[]]) => runIsolateTests( |
| args, |
| tests, |
| 'breakpoint_resolution_after_reloading_test.dart', |
| testeeConcurrent: testeeMain, |
| ); |