Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aggressively seal classes in package:devtools_app_shared #6235

Merged
merged 9 commits into from
Aug 24, 2023

Conversation

kenzieschmoll
Copy link
Member

@kenzieschmoll kenzieschmoll commented Aug 22, 2023

devtools_app_shared will be published on pub and will be used by DevTools extension authors to build extensions. In order to limit breaking changes to DevTools extensions, we would like to "seal" as many classes in this shared library as possible.

This PR aggressively marks classes as final, where possible. In cases where marking classes final would prohibit us from being able to effectively write tests, we use the @sealed annotation from package:meta.

@bkonyi @jacob314

@bkonyi
Copy link
Contributor

bkonyi commented Aug 22, 2023

It feels weird using @sealed and final in the same file, but I think this is the right thing to do for testability.

I think we should be very aggressive with marking anything we expose through this package as final or some variation of final. We can always relax restrictions if we have a reason to, but once we release we can't tighten them.

@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file should be reviewed

@@ -161,6 +161,13 @@ MockVmServiceWrapper createMockVmServiceWrapperWithDefaults() {
return service;
}

MockServiceConnectionManager createMockServiceConnectionWithDefaults() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new code

@@ -20,35 +20,103 @@ import 'fake_vm_service_wrapper.dart';
import 'generated.mocks.dart';
import 'mocks.dart';

class FakeServiceManager extends Fake implements ServiceConnectionManager {
FakeServiceManager({
class FakeServiceConnectionManager extends Fake
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file should be reviewed

@@ -7,6 +7,7 @@ import 'dart:core';

import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:meta/meta.dart';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file should be reviewed

@@ -188,3 +193,40 @@ class ConnectedApp {
flutterVersionKey: flutterVersionNow!.version,
};
}

final class OfflineConnectedApp extends ConnectedApp {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved from package:devtools_app and marked final

@@ -150,7 +196,8 @@ class ServiceConnectionManager extends ServiceManager<VmServiceWrapper> {
///
/// Throws an Exception if no 'FlutterView' is present in this isolate.
Future<String> get flutterViewId async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

superficially extensions like this seem equally useful for devtools extensions as for devtools proper.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed we may want to expose this. For now, I'd like to keep the API small and add more to devtools_app_shared as it makes sense.

@override
List<IsolateRef> isolatesFromVm(VM? vm) {
return vm?.isolatesForDevToolsMode() ?? <IsolateRef>[];
Future<String?> rootLibraryForMainIsolate() async {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like something an extension might want to know. how do you draw the line on whether to include it in the shared code or not?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed we may want to expose this. For now, I'd like to keep the API small and add more to devtools_app_shared as it makes sense. At this point I am exposing everything needed for the package:provider extension as a customer zero, and will expose more as we get a better idea of what more initial customers need.

@jacob314
Copy link
Contributor

It feels weird using @sealed and final in the same file, but I think this is the right thing to do for testability.

I think we should be very aggressive with marking anything we expose through this package as final or some variation of final. We can always relax restrictions if we have a reason to, but once we release we can't tighten them.

Turns out there is an alternative to using @sealed that still allows reopening classes for testing purposes only with only a couple lines of boilerplate.
dart-lang/language#3106 (comment)
Given that, I believe we should deprecate @sealed to avoid confusion and point users to this technique.

Copy link
Contributor

@bkonyi bkonyi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but we should probably get another +1 as I had a hard time reviewing this thoroughly (Chrome + GitHub are thrashing hard when I open the "Files changed" tab).

@@ -33,7 +35,35 @@ const debugLogServiceProtocolEvents = false;

const defaultRefreshRate = 60.0;

class ServiceConnectionManager extends ServiceManager<VmServiceWrapper> {
class ServiceConnectionManager {
ServiceConnectionManager() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can't this be ServiceConnectionManager() : serviceManager = ServiceManager().. //...? Then I think you can remove the late from the declaration of serviceManager as well.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the callbacks can't be accessed in an initializer.

@bkonyi
Copy link
Contributor

bkonyi commented Aug 24, 2023

Also, do we plan on using the solution from dart-lang/language#3106 (comment) or landing with @sealed for now?

@kenzieschmoll
Copy link
Member Author

Also, do we plan on using the solution from dart-lang/language#3106 (comment) or landing with @sealed for now?

In this comment here, I explain why we need to use @sealed right now for classes we need to mock and not fake. I also filed #6239 to track removing @sealed if we come up with a better solution or decide to rewrite our tests to not use mocks for these "sealed" classes.

Copy link
Contributor

@CoderDake CoderDake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems alright to me, but it was definitely a lot to review :P

Copy link
Contributor

@CoderDake CoderDake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops I meant to approve

@kenzieschmoll kenzieschmoll merged commit 7ff0097 into flutter:master Aug 24, 2023
15 checks passed
@kenzieschmoll kenzieschmoll deleted the example-dtas branch August 24, 2023 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants