1 // Copyright 2019 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.
4
5 #include "base/fuchsia/intl_profile_watcher.h"
6
7 #include <fuchsia/intl/cpp/fidl_test_base.h>
8 #include <lib/fidl/cpp/binding.h>
9 #include <memory>
10 #include <string>
11 #include <vector>
12
13 #include "base/check.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/run_loop.h"
16 #include "base/test/bind.h"
17 #include "base/test/mock_callback.h"
18 #include "base/test/task_environment.h"
19 #include "base/threading/sequence_bound.h"
20 #include "base/threading/thread.h"
21 #include "base/time/time.h"
22 #include "testing/gmock/include/gmock/gmock.h"
23 #include "testing/gtest/include/gtest/gtest.h"
24
25 namespace base {
26
27 namespace {
28
29 const char kPrimaryTimeZoneName[] = "Australia/Darwin";
30 const char kSecondaryTimeZoneName[] = "Africa/Djibouti";
31
32 const char kPrimaryLocaleName[] = "en-US";
33 const char kSecondaryLocaleName[] = "es-419";
34
35 template <typename FuchsiaStruct>
CopyIdsToFuchsiaStruct(const std::vector<std::string> & raw_ids,std::vector<FuchsiaStruct> * fuchsia_ids)36 void CopyIdsToFuchsiaStruct(const std::vector<std::string>& raw_ids,
37 std::vector<FuchsiaStruct>* fuchsia_ids) {
38 fuchsia_ids->clear();
39 for (auto id : raw_ids) {
40 FuchsiaStruct fuchsia_id;
41 fuchsia_id.id = id;
42 fuchsia_ids->push_back(fuchsia_id);
43 }
44 }
45
CreateProfileWithTimeZones(const std::vector<std::string> & zone_ids)46 fuchsia::intl::Profile CreateProfileWithTimeZones(
47 const std::vector<std::string>& zone_ids) {
48 fuchsia::intl::Profile profile;
49 std::vector<::fuchsia::intl::TimeZoneId> time_zone_ids;
50 CopyIdsToFuchsiaStruct(zone_ids, &time_zone_ids);
51 profile.set_time_zones(time_zone_ids);
52 return profile;
53 }
54
CreateProfileWithLocales(const std::vector<std::string> & locale_ids)55 fuchsia::intl::Profile CreateProfileWithLocales(
56 const std::vector<std::string>& locale_ids) {
57 fuchsia::intl::Profile profile;
58 std::vector<::fuchsia::intl::LocaleId> fuchsia_locale_ids;
59 CopyIdsToFuchsiaStruct(locale_ids, &fuchsia_locale_ids);
60 profile.set_locales(fuchsia_locale_ids);
61 return profile;
62 }
63
64 // Partial fake implementation of a PropertyProvider.
65 class FakePropertyProvider
66 : public ::fuchsia::intl::testing::PropertyProvider_TestBase {
67 public:
FakePropertyProvider(fidl::InterfaceRequest<::fuchsia::intl::PropertyProvider> provider_request)68 explicit FakePropertyProvider(
69 fidl::InterfaceRequest<::fuchsia::intl::PropertyProvider>
70 provider_request)
71 : binding_(this) {
72 binding_.Bind(std::move(provider_request));
73 DCHECK(binding_.is_bound());
74 }
75 FakePropertyProvider(const FakePropertyProvider&) = delete;
76 FakePropertyProvider& operator=(const FakePropertyProvider&) = delete;
77 ~FakePropertyProvider() override = default;
78
Close()79 void Close() { binding_.Close(ZX_ERR_PEER_CLOSED); }
SetTimeZones(const std::vector<std::string> & zone_ids)80 void SetTimeZones(const std::vector<std::string>& zone_ids) {
81 CopyIdsToFuchsiaStruct(zone_ids, &time_zone_ids_);
82 }
SetLocales(const std::vector<std::string> & locale_ids)83 void SetLocales(const std::vector<std::string>& locale_ids) {
84 CopyIdsToFuchsiaStruct(locale_ids, &fuchsia_locale_ids_);
85 }
NotifyChange()86 void NotifyChange() { binding_.events().OnChange(); }
87
88 // PropertyProvider_TestBase implementation.
GetProfile(::fuchsia::intl::PropertyProvider::GetProfileCallback callback)89 void GetProfile(
90 ::fuchsia::intl::PropertyProvider::GetProfileCallback callback) override {
91 fuchsia::intl::Profile profile;
92 profile.set_time_zones(time_zone_ids_);
93 profile.set_locales(fuchsia_locale_ids_);
94 callback(std::move(profile));
95 }
NotImplemented_(const std::string & name)96 void NotImplemented_(const std::string& name) override {
97 ADD_FAILURE() << "Unimplemented function called: " << name;
98 }
99
100 private:
101 ::fidl::Binding<::fuchsia::intl::PropertyProvider> binding_;
102
103 std::vector<::fuchsia::intl::TimeZoneId> time_zone_ids_;
104 std::vector<::fuchsia::intl::LocaleId> fuchsia_locale_ids_;
105 };
106
107 class FakePropertyProviderAsync {
108 public:
FakePropertyProviderAsync(fidl::InterfaceRequest<::fuchsia::intl::PropertyProvider> provider_request)109 explicit FakePropertyProviderAsync(
110 fidl::InterfaceRequest<::fuchsia::intl::PropertyProvider>
111 provider_request)
112 : thread_("Property Provider Thread") {
113 CHECK(thread_.StartWithOptions(
114 base::Thread::Options(base::MessagePumpType::IO, 0)));
115 property_provider_ = base::SequenceBound<FakePropertyProvider>(
116 thread_.task_runner(), std::move(provider_request));
117 }
118 FakePropertyProviderAsync(const FakePropertyProviderAsync&) = delete;
119 FakePropertyProviderAsync& operator=(const FakePropertyProviderAsync&) =
120 delete;
121 ~FakePropertyProviderAsync() = default;
122
Close()123 void Close() { property_provider_.AsyncCall(&FakePropertyProvider::Close); }
SetTimeZones(const std::vector<std::string> & zone_ids)124 void SetTimeZones(const std::vector<std::string>& zone_ids) {
125 property_provider_.AsyncCall(&FakePropertyProvider::SetTimeZones)
126 .WithArgs(zone_ids);
127 }
SetLocales(const std::vector<std::string> & locale_ids)128 void SetLocales(const std::vector<std::string>& locale_ids) {
129 property_provider_.AsyncCall(&FakePropertyProvider::SetLocales)
130 .WithArgs(locale_ids);
131 }
NotifyChange()132 void NotifyChange() {
133 property_provider_.AsyncCall(&FakePropertyProvider::NotifyChange);
134 }
135
136 private:
137 base::Thread thread_;
138 base::SequenceBound<FakePropertyProvider> property_provider_;
139 };
140
141 } // namespace
142
143 class GetValuesFromIntlPropertyProviderTest : public testing::Test {
144 public:
GetValuesFromIntlPropertyProviderTest()145 GetValuesFromIntlPropertyProviderTest()
146 : property_provider_(property_provider_ptr_.NewRequest()) {}
147 GetValuesFromIntlPropertyProviderTest(
148 const GetValuesFromIntlPropertyProviderTest&) = delete;
149 GetValuesFromIntlPropertyProviderTest& operator=(
150 const GetValuesFromIntlPropertyProviderTest&) = delete;
151 ~GetValuesFromIntlPropertyProviderTest() override = default;
152
153 protected:
GetPrimaryLocaleId()154 std::string GetPrimaryLocaleId() {
155 fuchsia::intl::Profile profile =
156 GetProfileFromPropertyProvider(std::move(property_provider_ptr_));
157 return FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(profile);
158 }
159
GetPrimaryTimeZoneId()160 std::string GetPrimaryTimeZoneId() {
161 fuchsia::intl::Profile profile =
162 GetProfileFromPropertyProvider(std::move(property_provider_ptr_));
163 return FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(profile);
164 }
165
GetProfileFromPropertyProvider(::fuchsia::intl::PropertyProviderSyncPtr property_provider)166 static fuchsia::intl::Profile GetProfileFromPropertyProvider(
167 ::fuchsia::intl::PropertyProviderSyncPtr property_provider) {
168 return FuchsiaIntlProfileWatcher::GetProfileFromPropertyProvider(
169 std::move(property_provider));
170 }
171
172 ::fuchsia::intl::PropertyProviderSyncPtr property_provider_ptr_;
173 FakePropertyProviderAsync property_provider_;
174 };
175
176 class IntlProfileWatcherTest : public testing::Test {
177 public:
IntlProfileWatcherTest()178 IntlProfileWatcherTest()
179 : property_provider_(property_provider_ptr_.NewRequest()) {}
180 IntlProfileWatcherTest(const IntlProfileWatcherTest&) = delete;
181 IntlProfileWatcherTest& operator=(const IntlProfileWatcherTest&) = delete;
182 ~IntlProfileWatcherTest() override = default;
183
184 protected:
185 base::test::SingleThreadTaskEnvironment task_environment_{
186 base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
187
CreateIntlProfileWatcher(FuchsiaIntlProfileWatcher::ProfileChangeCallback on_profile_changed)188 std::unique_ptr<FuchsiaIntlProfileWatcher> CreateIntlProfileWatcher(
189 FuchsiaIntlProfileWatcher::ProfileChangeCallback on_profile_changed) {
190 return base::WrapUnique(new FuchsiaIntlProfileWatcher(
191 std::move(property_provider_ptr_), std::move(on_profile_changed)));
192 }
193
194 ::fuchsia::intl::PropertyProviderPtr property_provider_ptr_;
195 FakePropertyProviderAsync property_provider_;
196
197 base::RunLoop run_loop_;
198 };
199
200 // Unit tests are run in an environment where intl is not provided.
201 // However, this is not exposed by the API.
TEST(IntlServiceNotAvailableTest,FuchsiaIntlProfileWatcher)202 TEST(IntlServiceNotAvailableTest, FuchsiaIntlProfileWatcher) {
203 base::test::SingleThreadTaskEnvironment task_environment_{
204 base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
205 base::RunLoop run_loop;
206
207 base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback>
208 on_profile_changed;
209 EXPECT_CALL(on_profile_changed, Run(testing::_)).Times(0);
210 auto watcher =
211 std::make_unique<FuchsiaIntlProfileWatcher>(on_profile_changed.Get());
212 EXPECT_TRUE(watcher);
213
214 run_loop.RunUntilIdle();
215 }
216
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryTimeZoneId_RemoteNotBound)217 TEST_F(GetValuesFromIntlPropertyProviderTest,
218 GetPrimaryTimeZoneId_RemoteNotBound) {
219 // Simulate the service not actually being available.
220 property_provider_.Close();
221 EXPECT_STREQ("", GetPrimaryTimeZoneId().c_str());
222 }
223
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryTimeZoneId_NoZones)224 TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryTimeZoneId_NoZones) {
225 EXPECT_STREQ("", GetPrimaryTimeZoneId().c_str());
226 }
227
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryTimeZoneId_SingleZone)228 TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryTimeZoneId_SingleZone) {
229 property_provider_.SetTimeZones({kPrimaryTimeZoneName});
230 EXPECT_STREQ(kPrimaryTimeZoneName, GetPrimaryTimeZoneId().c_str());
231 }
232
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryTimeZoneId_SingleZoneIsEmpty)233 TEST_F(GetValuesFromIntlPropertyProviderTest,
234 GetPrimaryTimeZoneId_SingleZoneIsEmpty) {
235 property_provider_.SetTimeZones({""});
236 EXPECT_STREQ("", GetPrimaryTimeZoneId().c_str());
237 }
238
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryTimeZoneId_MoreThanOneZone)239 TEST_F(GetValuesFromIntlPropertyProviderTest,
240 GetPrimaryTimeZoneId_MoreThanOneZone) {
241 property_provider_.SetTimeZones(
242 {kPrimaryTimeZoneName, kSecondaryTimeZoneName});
243 EXPECT_STREQ(kPrimaryTimeZoneName, GetPrimaryTimeZoneId().c_str());
244 }
245
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryLocaleId_RemoteNotBound)246 TEST_F(GetValuesFromIntlPropertyProviderTest,
247 GetPrimaryLocaleId_RemoteNotBound) {
248 // Simulate the service not actually being available.
249 property_provider_.Close();
250 EXPECT_STREQ("", GetPrimaryLocaleId().c_str());
251 }
252
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryLocaleId_NoZones)253 TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryLocaleId_NoZones) {
254 EXPECT_STREQ("", GetPrimaryLocaleId().c_str());
255 }
256
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryLocaleId_SingleLocale)257 TEST_F(GetValuesFromIntlPropertyProviderTest, GetPrimaryLocaleId_SingleLocale) {
258 property_provider_.SetLocales({kPrimaryLocaleName});
259 EXPECT_STREQ(kPrimaryLocaleName, GetPrimaryLocaleId().c_str());
260 }
261
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryLocaleId_SingleLocaleIsEmpty)262 TEST_F(GetValuesFromIntlPropertyProviderTest,
263 GetPrimaryLocaleId_SingleLocaleIsEmpty) {
264 property_provider_.SetLocales({""});
265 EXPECT_STREQ("", GetPrimaryLocaleId().c_str());
266 }
267
TEST_F(GetValuesFromIntlPropertyProviderTest,GetPrimaryLocaleId_MoreThanOneLocale)268 TEST_F(GetValuesFromIntlPropertyProviderTest,
269 GetPrimaryLocaleId_MoreThanOneLocale) {
270 property_provider_.SetLocales({kPrimaryLocaleName, kSecondaryLocaleName});
271 EXPECT_STREQ(kPrimaryLocaleName, GetPrimaryLocaleId().c_str());
272 }
273
TEST_F(IntlProfileWatcherTest,NoZones_NoNotification)274 TEST_F(IntlProfileWatcherTest, NoZones_NoNotification) {
275 base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback> callback;
276 EXPECT_CALL(callback, Run(testing::_)).Times(0);
277 auto watcher = CreateIntlProfileWatcher(callback.Get());
278 run_loop_.RunUntilIdle();
279 }
280
TEST_F(IntlProfileWatcherTest,ChangeNotification_AfterInitialization)281 TEST_F(IntlProfileWatcherTest, ChangeNotification_AfterInitialization) {
282 auto watcher = CreateIntlProfileWatcher(
283 base::BindLambdaForTesting([quit_loop = run_loop_.QuitClosure()](
284 const fuchsia::intl::Profile& profile) {
285 EXPECT_EQ(kPrimaryTimeZoneName,
286 FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
287 profile));
288 quit_loop.Run();
289 }));
290
291 property_provider_.SetTimeZones({kPrimaryTimeZoneName});
292 property_provider_.NotifyChange();
293
294 run_loop_.Run();
295 }
296
TEST_F(IntlProfileWatcherTest,ChangeNotification_BeforeInitialization)297 TEST_F(IntlProfileWatcherTest, ChangeNotification_BeforeInitialization) {
298 property_provider_.SetTimeZones({kPrimaryTimeZoneName});
299 property_provider_.NotifyChange();
300
301 auto watcher = CreateIntlProfileWatcher(
302 base::BindLambdaForTesting([quit_loop = run_loop_.QuitClosure()](
303 const fuchsia::intl::Profile& profile) {
304 EXPECT_EQ(kPrimaryTimeZoneName,
305 FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
306 profile));
307 quit_loop.Run();
308 }));
309
310 run_loop_.Run();
311 }
312
313 // Ensure no crash when the peer service cannot be reached during creation.
TEST_F(IntlProfileWatcherTest,ChannelClosedBeforeCreation)314 TEST_F(IntlProfileWatcherTest, ChannelClosedBeforeCreation) {
315 base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback> callback;
316 EXPECT_CALL(callback, Run(testing::_)).Times(0);
317
318 property_provider_.Close();
319
320 auto watcher = CreateIntlProfileWatcher(callback.Get());
321
322 property_provider_.NotifyChange();
323 run_loop_.RunUntilIdle();
324 }
325
326 // Ensure no crash when the channel is closed after creation.
TEST_F(IntlProfileWatcherTest,ChannelClosedAfterCreation)327 TEST_F(IntlProfileWatcherTest, ChannelClosedAfterCreation) {
328 base::MockCallback<FuchsiaIntlProfileWatcher::ProfileChangeCallback> callback;
329 EXPECT_CALL(callback, Run(testing::_)).Times(0);
330
331 auto watcher = CreateIntlProfileWatcher(callback.Get());
332
333 property_provider_.Close();
334
335 property_provider_.NotifyChange();
336 run_loop_.RunUntilIdle();
337 }
338
TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest,NoZones)339 TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, NoZones) {
340 EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
341 fuchsia::intl::Profile()));
342 }
343
TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest,EmptyZonesList)344 TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, EmptyZonesList) {
345 EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
346 CreateProfileWithTimeZones({})));
347 }
348
TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest,OneZone)349 TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, OneZone) {
350 EXPECT_EQ(kPrimaryTimeZoneName,
351 FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
352 CreateProfileWithTimeZones({kPrimaryTimeZoneName})));
353 }
354
TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest,TwoZones)355 TEST(IntlProfileWatcherGetPrimaryTimeZoneIdFromProfileTest, TwoZones) {
356 EXPECT_EQ(kPrimaryTimeZoneName,
357 FuchsiaIntlProfileWatcher::GetPrimaryTimeZoneIdFromProfile(
358 CreateProfileWithTimeZones(
359 {kPrimaryTimeZoneName, kSecondaryTimeZoneName})));
360 }
361
TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest,NoLocales)362 TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, NoLocales) {
363 EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
364 fuchsia::intl::Profile()));
365 }
366
TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest,EmptyLocalesList)367 TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, EmptyLocalesList) {
368 EXPECT_EQ("", FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
369 CreateProfileWithLocales({})));
370 }
371
TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest,OneLocale)372 TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, OneLocale) {
373 EXPECT_EQ(kPrimaryLocaleName,
374 FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
375 CreateProfileWithLocales({kPrimaryLocaleName})));
376 }
377
TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest,MultipleLocales)378 TEST(IntlProfileWatcherGetPrimaryLocaleIdFromProfileTest, MultipleLocales) {
379 EXPECT_EQ(kPrimaryLocaleName,
380 FuchsiaIntlProfileWatcher::GetPrimaryLocaleIdFromProfile(
381 CreateProfileWithLocales(
382 {kPrimaryLocaleName, kSecondaryLocaleName})));
383 }
384
385 } // namespace base
386