Skip to content

Commit

Permalink
[macOS] Clean up allocations in menu plugin test
Browse files Browse the repository at this point in the history
Runs all FlutterMenuPlugin tests in an AutoReleasepoolTest, which
ensures all allocations are cleaned up.

Also extracts out some hackery into the FlutterMenuPluginTest fixture to
ensure that NSApplication instantiates everything necessary to do menu
bar manipulation.

Also replaces the use of OCMock in FlutterMenuPluginTest.mm with a fake
FakePluginRegistrar class. This avoids unnecessary use of OCMock in the
tests, which has been responsible for flakiness in some tests, in
particular where the mock is used across threads. This test was not
problematic, but the fake makes the tests more readable.

Issue: flutter/flutter#104789
Issue: flutter/flutter#127441
Issue: flutter/flutter#124840
  • Loading branch information
cbracken committed Nov 15, 2023
1 parent 30327ea commit 23a1c22
Showing 1 changed file with 69 additions and 42 deletions.
111 changes: 69 additions & 42 deletions shell/platform/darwin/macos/framework/Source/FlutterMenuPluginTest.mm
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,86 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin_Internal.h"

#import "flutter/shell/platform/common/platform_provided_menu.h"
#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginMacOS.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterPluginRegistrarMacOS.h"
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMenuPlugin_Internal.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputSemanticsObject.h"
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"

#include "flutter/shell/platform/common/platform_provided_menu.h"
#include "flutter/testing/autoreleasepool_test.h"
#include "flutter/testing/testing.h"
#include "gtest/gtest.h"

#import <OCMock/OCMock.h>
#import "flutter/testing/testing.h"
@interface FakePluginRegistrar : NSObject <FlutterPluginRegistrar>
@property(nonatomic, readonly) id<FlutterPlugin> plugin;
@property(nonatomic, readonly) FlutterMethodChannel* channel;
@end

@implementation FakePluginRegistrar
@synthesize messenger;
@synthesize textures;
@synthesize view;

- (void)addMethodCallDelegate:(nonnull id<FlutterPlugin>)delegate
channel:(nonnull FlutterMethodChannel*)channel {
_plugin = delegate;
_channel = channel;
[_channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[delegate handleMethodCall:call result:result];
}];
}

- (void)addApplicationDelegate:(nonnull NSObject<FlutterAppLifecycleDelegate>*)delegate {
}

- (void)registerViewFactory:(nonnull NSObject<FlutterPlatformViewFactory>*)factory
withId:(nonnull NSString*)factoryId {
}

@interface FlutterMenuPluginTestObjc : NSObject
- (bool)testSetMenu;
- (void)publish:(nonnull NSObject*)value {
}

- (nonnull NSString*)lookupKeyForAsset:(nonnull NSString*)asset {
return @"";
}

- (nonnull NSString*)lookupKeyForAsset:(nonnull NSString*)asset
fromPackage:(nonnull NSString*)package {
return @"";
}
@end

@implementation FlutterMenuPluginTestObjc
namespace flutter::testing {

- (bool)testSetMenu {
// Workaround to deflake the test.
// See: https://github.com/flutter/flutter/issues/104748#issuecomment-1159336728
NSView* view = [[NSView alloc] initWithFrame:NSZeroRect];
view.wantsLayer = YES;
// FlutterMenuPluginTest is an AutoreleasePoolTest that allocates an NSView.
//
// This supports the use of NSApplication features that rely on the assumption of a view, such as
// when modifying the application menu bar, or even accessing the NSApplication.localizedName
// property.
//
// See: https://github.com/flutter/flutter/issues/104748#issuecomment-1159336728
class FlutterMenuPluginTest : public AutoreleasePoolTest {
public:
FlutterMenuPluginTest();
~FlutterMenuPluginTest() = default;

private:
NSView* view_;
};

FlutterMenuPluginTest::FlutterMenuPluginTest() {
view_ = [[NSView alloc] initWithFrame:NSZeroRect];
view_.wantsLayer = YES;
}

TEST_F(FlutterMenuPluginTest, TestSetMenu) {
// Build a simulation of the default main menu.
NSMenu* mainMenu = [[NSMenu alloc] init];
NSMenuItem* appNameMenu = [[NSMenuItem alloc] initWithTitle:@"APP_NAME"
Expand All @@ -43,26 +95,9 @@ - (bool)testSetMenu {
[mainMenu addItem:appNameMenu];
[NSApp setMainMenu:mainMenu];

id<FlutterPluginRegistrar> pluginRegistrarMock =
OCMProtocolMock(@protocol(FlutterPluginRegistrar));
__block FlutterMethodChannel* pluginChannel;
__block FlutterMenuPlugin* plugin;
id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
OCMStub([pluginRegistrarMock messenger]).andReturn(binaryMessengerMock);
OCMStub( // NOLINT(google-objc-avoid-throwing-exception)
[pluginRegistrarMock addMethodCallDelegate:[OCMArg any] channel:[OCMArg any]])
.andDo(^(NSInvocation* invocation) {
id<FlutterPlugin> delegate;
FlutterMethodChannel* channel;
[invocation getArgument:&delegate atIndex:2];
[invocation getArgument:&channel atIndex:3];
pluginChannel = channel;
plugin = delegate;
[channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
[delegate handleMethodCall:call result:result];
}];
});
[FlutterMenuPlugin registerWithRegistrar:pluginRegistrarMock];
FakePluginRegistrar* registrar = [[FakePluginRegistrar alloc] init];
[FlutterMenuPlugin registerWithRegistrar:registrar];
FlutterMenuPlugin* plugin = [registrar plugin];

NSDictionary* testMenus = @{
@"0" : @[
Expand Down Expand Up @@ -173,14 +208,6 @@ - (bool)testSetMenu {
EXPECT_TRUE(
[NSStringFromSelector([secondMenuLast action]) isEqualToString:@"flutterMenuItemSelected:"]);
EXPECT_EQ([secondMenuLast tag], 7);

return true;
}

@end

namespace flutter::testing {
TEST(FlutterMenuPluginTest, TestSetMenu) {
ASSERT_TRUE([[FlutterMenuPluginTestObjc alloc] testSetMenu]);
}
} // namespace flutter::testing

0 comments on commit 23a1c22

Please sign in to comment.