1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
7 #include "base/files/file_path.h"
8 #include "base/memory/raw_ptr.h"
9 #include "base/strings/strcat.h"
10 #include "base/test/bind.h"
11 #include "build/build_config.h"
12 #include "build/chromeos_buildflags.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/chrome_browser_main.h"
15 #include "chrome/browser/chrome_browser_main_extra_parts.h"
16 #include "chrome/browser/profiles/profile_attributes_entry.h"
17 #include "chrome/browser/profiles/profile_attributes_storage.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/browser/profiles/profile_test_util.h"
20 #include "chrome/browser/profiles/profile_window.h"
21 #include "chrome/browser/ui/browser.h"
22 #include "chrome/browser/ui/browser_finder.h"
23 #include "chrome/browser/ui/profiles/profile_picker.h"
24 #include "chrome/common/chrome_constants.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/test/base/in_process_browser_test.h"
27 #include "components/prefs/pref_service.h"
28 #include "content/public/test/browser_test.h"
29 #include "testing/gmock/include/gmock/gmock.h"
32 using ::testing::InSequence;
33 using ::testing::Matcher;
34 using ::testing::Mock;
35 using ::testing::Property;
36 using ::testing::ValuesIn;
38 #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_CHROMEOS_ASH)
39 #error Not supported on this platform.
44 class MockMainExtraParts : public ChromeBrowserMainExtraParts {
46 MOCK_METHOD(void, PreProfileInit, ());
47 MOCK_METHOD(void, PostProfileInit, (Profile*, bool));
48 MOCK_METHOD(void, PreBrowserStart, ());
49 MOCK_METHOD(void, PostBrowserStart, ());
50 MOCK_METHOD(void, PreMainMessageLoopRun, ());
53 const char kOtherProfileDirPath[] = "Other";
55 MATCHER_P(BaseNameEquals,
57 base::StrCat({negation ? "doesn't equal " : "equals ", basename})) {
58 return arg == base::FilePath::FromASCII(basename);
61 Matcher<Profile*> HasBaseName(const char* basename) {
62 return Property("basename", &Profile::GetBaseName, BaseNameEquals(basename));
65 struct MultiProfileStartupTestParam {
66 // Whether the profile picker should be shown on startup.
67 const bool should_show_profile_picker;
69 struct PostInitExpectedCall {
70 // Matcher for the expected `profile` argument to `PostProfileInit()`
71 const Matcher<Profile*> profile_matcher;
73 // Expected value for the `is_initial_profile` argument to
74 // `PostProfileInit()`
75 const bool is_initial_profile;
78 // Call expectations for the `PostProfileInit()` method. The expectations
79 // should themselves be listed in the expected call order.
81 // The first one is checked in `CreatedBrowserMainParts()` as part of startup,
82 // and the remaining ones in the test body.
83 const std::vector<PostInitExpectedCall> expected_post_profile_init_call_args;
86 const MultiProfileStartupTestParam kTestParams[] = {
87 {.should_show_profile_picker = false,
88 .expected_post_profile_init_call_args =
89 {{HasBaseName(chrome::kInitialProfile), true},
90 {HasBaseName(kOtherProfileDirPath), false}}},
91 {.should_show_profile_picker = true,
92 .expected_post_profile_init_call_args = {
93 {HasBaseName(chrome::kInitialProfile), true},
94 {HasBaseName(kOtherProfileDirPath), false}}}};
96 // Creates a new profile to be picked up on the actual test.
97 void SetUpSecondaryProfileForPreTest(
98 const base::FilePath& profile_dir_basename) {
99 ProfileManager* profile_manager = g_browser_process->profile_manager();
100 base::FilePath profile_path =
101 profile_manager->user_data_dir().Append(profile_dir_basename);
103 profiles::testing::CreateProfileSync(profile_manager, profile_path);
105 // Mark newly created profile as active.
106 ProfileAttributesEntry* entry =
107 profile_manager->GetProfileAttributesStorage()
108 .GetProfileAttributesWithPath(profile_path);
109 ASSERT_NE(entry, nullptr);
110 entry->SetActiveTimeToNow();
113 void CreateBrowserForProfileDir(const base::FilePath& profile_dir_basename) {
114 profiles::testing::SwitchToProfileSync(
115 g_browser_process->profile_manager()->user_data_dir().Append(
116 profile_dir_basename));
121 class ChromeMultiProfileStartupBrowserTestBase
122 : public InProcessBrowserTest,
123 public testing::WithParamInterface<MultiProfileStartupTestParam> {
125 ChromeMultiProfileStartupBrowserTestBase() {
126 // Avoid providing a URL for the browser to open, allows the profile picker
127 // to be displayed on startup when it is enabled.
128 set_open_about_blank_on_browser_launch(false);
131 void CreatedBrowserMainParts(content::BrowserMainParts* parts) override {
132 InProcessBrowserTest::CreatedBrowserMainParts(parts);
134 // Skip expectations preparation for the PRE_ step.
135 if (GetTestPreCount() != 0)
138 auto mock_part = std::make_unique<MockMainExtraParts>();
139 mock_part_ = mock_part.get();
140 static_cast<ChromeBrowserMainParts*>(parts)->AddParts(std::move(mock_part));
142 // At least one entry for the initial call is needed.
143 ASSERT_FALSE(GetParam().expected_post_profile_init_call_args.empty());
145 // The basic callbacks should be called only once.
146 EXPECT_CALL(*mock_part_, PreProfileInit()).Times(1);
147 EXPECT_CALL(*mock_part_, PreBrowserStart()).Times(1);
148 EXPECT_CALL(*mock_part_, PostBrowserStart()).Times(1);
149 EXPECT_CALL(*mock_part_, PreMainMessageLoopRun()).Times(1);
152 const auto& call_args = GetParam().expected_post_profile_init_call_args;
154 for (const auto& expected_args : call_args) {
155 EXPECT_CALL(*mock_part_,
156 PostProfileInit(expected_args.profile_matcher,
157 expected_args.is_initial_profile));
162 raw_ptr<MockMainExtraParts, AcrossTasksDanglingUntriaged> mock_part_;
165 IN_PROC_BROWSER_TEST_P(ChromeMultiProfileStartupBrowserTestBase,
166 PRE_PostProfileInitInvocation) {
167 SetUpSecondaryProfileForPreTest(
168 base::FilePath::FromASCII(kOtherProfileDirPath));
169 g_browser_process->local_state()->SetBoolean(
170 prefs::kBrowserShowProfilePickerOnStartup,
171 GetParam().should_show_profile_picker);
173 // Need to close the browser window manually so that the real test does not
174 // treat it as session restore.
178 // Make sure that the second profile creation causes `PostProfileInit()` to be
179 // called a second time.
180 IN_PROC_BROWSER_TEST_P(ChromeMultiProfileStartupBrowserTestBase,
181 PostProfileInitInvocation) {
182 EXPECT_EQ(2u, g_browser_process->profile_manager()->GetNumberOfProfiles());
183 if (GetParam().should_show_profile_picker) {
184 EXPECT_EQ(0u, chrome::GetTotalBrowserCount());
185 EXPECT_TRUE(ProfilePicker::IsOpen());
187 EXPECT_EQ(1u, chrome::GetTotalBrowserCount());
188 EXPECT_NE(base::FilePath::FromASCII(kOtherProfileDirPath),
189 browser()->profile()->GetPath().BaseName());
190 EXPECT_FALSE(ProfilePicker::IsOpen());
193 // TODO(https://crbug.com/1288766): In some cases, profile creation is
194 // triggered by restoring the previously opened profile, and the test
195 // expectations in terms of `PostProfileInit()` calls can
196 // be met without opening browsers. We still open them for consistency, at
197 // least until we can make the test behaviour stricter.
198 if (GetParam().should_show_profile_picker) {
199 // No browser was previously open, as verified at the beginning of the test.
200 // So we start by opening the browser for the default profile.
201 CreateBrowserForProfileDir(
202 base::FilePath::FromASCII(chrome::kInitialProfile));
204 CreateBrowserForProfileDir(base::FilePath::FromASCII(kOtherProfileDirPath));
206 EXPECT_EQ(2u, chrome::GetTotalBrowserCount());
209 INSTANTIATE_TEST_SUITE_P(All,
210 ChromeMultiProfileStartupBrowserTestBase,
211 ValuesIn(kTestParams));