1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "gtest/gtest.h"
8 #include "gmock/gmock.h"
9 #include "nsIWifiListener.h"
10 #include "nsWifiMonitor.h"
11 #include "nsWifiAccessPoint.h"
12 #include "WifiScanner.h"
14 #include "mozilla/Preferences.h"
15 #include "mozilla/Services.h"
16 #include "nsIObserverService.h"
17 #include "nsINetworkLinkService.h"
18 #include "mozilla/SpinEventLoopUntil.h"
20 #include "nsServiceManagerUtils.h"
22 #if defined(XP_WIN) && defined(_M_IX86)
23 # include <objbase.h> // STDMETHODCALLTYPE
26 // Tests that wifi scanning happens on the right network change events,
27 // and that wifi-scan polling is operable on mobile networks.
29 using ::testing::AtLeast
;
30 using ::testing::Cardinality
;
31 using ::testing::Exactly
;
32 using ::testing::MockFunction
;
33 using ::testing::Sequence
;
35 static mozilla::LazyLogModule
gLog("TestWifiMonitor");
36 #define LOGI(x) MOZ_LOG(gLog, mozilla::LogLevel::Info, x)
37 #define LOGD(x) MOZ_LOG(gLog, mozilla::LogLevel::Debug, x)
41 // Timeout if update not received from wifi scanner thread.
42 static const uint32_t kWifiScanTestResultTimeoutMs
= 100;
43 static const uint32_t kTestWifiScanIntervalMs
= 10;
45 // ID counter, used to make sure each call to GetAccessPointsFromWLAN
46 // returns "new" access points.
47 static int gCurrentId
= 0;
49 static uint32_t gNumScanResults
= 0;
51 struct LinkTypeMobility
{
52 const char* mLinkType
;
56 class MockWifiScanner
: public WifiScanner
{
58 MOCK_METHOD(nsresult
, GetAccessPointsFromWLAN
,
59 (nsTArray
<RefPtr
<nsIWifiAccessPoint
>> & aAccessPoints
),
63 class MockWifiListener
: public nsIWifiListener
{
64 virtual ~MockWifiListener() = default;
67 NS_DECL_THREADSAFE_ISUPPORTS
68 #if defined(XP_WIN) && defined(_M_IX86)
69 MOCK_METHOD(nsresult
, OnChange
,
70 (const nsTArray
<RefPtr
<nsIWifiAccessPoint
>>& accessPoints
),
71 (override
, Calltype(STDMETHODCALLTYPE
)));
72 MOCK_METHOD(nsresult
, OnError
, (nsresult error
),
73 (override
, Calltype(STDMETHODCALLTYPE
)));
75 MOCK_METHOD(nsresult
, OnChange
,
76 (const nsTArray
<RefPtr
<nsIWifiAccessPoint
>>& accessPoints
),
78 MOCK_METHOD(nsresult
, OnError
, (nsresult error
), (override
));
82 NS_IMPL_ISUPPORTS(MockWifiListener
, nsIWifiListener
)
84 class TestWifiMonitor
: public ::testing::Test
{
87 mObs
= mozilla::services::GetObserverService();
88 MOZ_RELEASE_ASSERT(mObs
);
91 nsCOMPtr
<nsINetworkLinkService
> nls
=
92 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID
, &rv
);
93 EXPECT_TRUE(NS_SUCCEEDED(rv
));
95 rv
= nls
->GetLinkType(&mOrigLinkType
);
96 EXPECT_TRUE(NS_SUCCEEDED(rv
));
97 rv
= nls
->GetIsLinkUp(&mOrigIsLinkUp
);
98 EXPECT_TRUE(NS_SUCCEEDED(rv
));
99 rv
= nls
->GetLinkStatusKnown(&mOrigLinkStatusKnown
);
100 EXPECT_TRUE(NS_SUCCEEDED(rv
));
102 // Reduce wifi-polling interval. 0 turns polling off.
103 mOldScanInterval
= Preferences::GetInt(WIFI_SCAN_INTERVAL_MS_PREF
);
104 Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF
, kTestWifiScanIntervalMs
);
108 Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF
, mOldScanInterval
);
110 // Restore network link type
111 const char* linkType
= nullptr;
112 switch (mOrigLinkType
) {
113 case nsINetworkLinkService::LINK_TYPE_UNKNOWN
:
114 linkType
= NS_NETWORK_LINK_TYPE_UNKNOWN
;
116 case nsINetworkLinkService::LINK_TYPE_ETHERNET
:
117 linkType
= NS_NETWORK_LINK_TYPE_ETHERNET
;
119 case nsINetworkLinkService::LINK_TYPE_USB
:
120 linkType
= NS_NETWORK_LINK_TYPE_USB
;
122 case nsINetworkLinkService::LINK_TYPE_WIFI
:
123 linkType
= NS_NETWORK_LINK_TYPE_WIFI
;
125 case nsINetworkLinkService::LINK_TYPE_MOBILE
:
126 linkType
= NS_NETWORK_LINK_TYPE_MOBILE
;
128 case nsINetworkLinkService::LINK_TYPE_WIMAX
:
129 linkType
= NS_NETWORK_LINK_TYPE_WIMAX
;
132 EXPECT_TRUE(linkType
);
133 mObs
->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC
,
134 NS_ConvertUTF8toUTF16(linkType
).get());
136 const char* linkStatus
= nullptr;
137 if (mOrigLinkStatusKnown
) {
139 linkStatus
= NS_NETWORK_LINK_DATA_UP
;
141 linkStatus
= NS_NETWORK_LINK_DATA_DOWN
;
144 linkStatus
= NS_NETWORK_LINK_DATA_UNKNOWN
;
146 EXPECT_TRUE(linkStatus
);
147 mObs
->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC
,
148 NS_ConvertUTF8toUTF16(linkStatus
).get());
152 bool WaitForScanResults() {
153 // Wait for kWifiScanTestResultTimeoutMs to allow async calls to complete.
154 bool timedout
= false;
155 RefPtr
<CancelableRunnable
> timer
= NS_NewCancelableRunnableFunction(
156 "WaitForScanResults Timeout", [&] { timedout
= true; });
157 NS_DelayedDispatchToCurrentThread(do_AddRef(timer
),
158 kWifiScanTestResultTimeoutMs
);
160 mozilla::SpinEventLoopUntil("TestWifiMonitor::WaitForScanResults"_ns
,
161 [&]() { return timedout
; });
167 void CreateObjects() {
168 mWifiMonitor
= MakeRefPtr
<nsWifiMonitor
>(MakeUnique
<MockWifiScanner
>());
169 EXPECT_TRUE(!mWifiMonitor
->IsPolling());
171 // Start with ETHERNET network type to avoid always polling at test start.
172 mObs
->NotifyObservers(
173 nullptr, NS_NETWORK_LINK_TYPE_TOPIC
,
174 NS_ConvertUTF8toUTF16(NS_NETWORK_LINK_TYPE_ETHERNET
).get());
176 mWifiListener
= new MockWifiListener();
177 LOGI(("monitor: %p | scanner: %p | listener: %p", mWifiMonitor
.get(),
178 mWifiMonitor
->mWifiScanner
.get(), mWifiListener
.get()));
181 void DestroyObjects() {
182 ::testing::Mock::VerifyAndClearExpectations(
183 mWifiMonitor
->mWifiScanner
.get());
184 ::testing::Mock::VerifyAndClearExpectations(mWifiListener
.get());
186 // Manually disconnect observers so that the monitor can be destroyed.
187 // In the browser, this would be done on xpcom-shutdown but that is sent
188 // after the tests run, which is too late to avoid a gtest memory-leak
190 mWifiMonitor
->Close();
192 mWifiMonitor
= nullptr;
193 mWifiListener
= nullptr;
197 void StartWatching(bool aRequestPolling
) {
198 LOGD(("StartWatching | aRequestPolling: %s | nScanResults: %u",
199 aRequestPolling
? "true" : "false", gNumScanResults
));
200 EXPECT_TRUE(NS_SUCCEEDED(
201 mWifiMonitor
->StartWatching(mWifiListener
, aRequestPolling
)));
202 WaitForScanResults();
205 void NotifyOfNetworkEvent(const char* aTopic
, const char16_t
* aData
) {
206 LOGD(("NotifyOfNetworkEvent: (%s, %s) | nScanResults: %u", aTopic
,
207 NS_ConvertUTF16toUTF8(aData
).get(), gNumScanResults
));
208 EXPECT_TRUE(NS_SUCCEEDED(mObs
->NotifyObservers(nullptr, aTopic
, aData
)));
209 WaitForScanResults();
212 void StopWatching() {
213 LOGD(("StopWatching | nScanResults: %u", gNumScanResults
));
214 EXPECT_TRUE(NS_SUCCEEDED(mWifiMonitor
->StopWatching(mWifiListener
)));
215 WaitForScanResults();
218 struct MockCallSequences
{
219 Sequence mGetAccessPointsSeq
;
220 Sequence mOnChangeSeq
;
221 Sequence mOnErrorSeq
;
224 void AddMockObjectChecks(const Cardinality
& aScanCardinality
,
225 MockCallSequences
& aSeqs
) {
226 // Only add WillRepeatedly handler if scans is more than 0, to avoid a
227 // VERY LOUD gtest warning.
228 if (aScanCardinality
.IsSaturatedByCallCount(0)) {
230 *static_cast<MockWifiScanner
*>(mWifiMonitor
->mWifiScanner
.get()),
231 GetAccessPointsFromWLAN
)
232 .Times(aScanCardinality
)
233 .InSequence(aSeqs
.mGetAccessPointsSeq
);
235 EXPECT_CALL(*mWifiListener
, OnChange
)
236 .Times(aScanCardinality
)
237 .InSequence(aSeqs
.mOnChangeSeq
);
240 *static_cast<MockWifiScanner
*>(mWifiMonitor
->mWifiScanner
.get()),
241 GetAccessPointsFromWLAN
)
242 .Times(aScanCardinality
)
243 .InSequence(aSeqs
.mGetAccessPointsSeq
)
245 [](nsTArray
<RefPtr
<nsIWifiAccessPoint
>>& aAccessPoints
) {
246 EXPECT_TRUE(!NS_IsMainThread());
247 EXPECT_TRUE(aAccessPoints
.IsEmpty());
248 nsWifiAccessPoint
* ap
= new nsWifiAccessPoint();
249 // Signal will be unique so we won't match the prior access
251 ap
->mSignal
= gCurrentId
++;
252 aAccessPoints
.AppendElement(RefPtr(ap
));
256 EXPECT_CALL(*mWifiListener
, OnChange
)
257 .Times(aScanCardinality
)
258 .InSequence(aSeqs
.mOnChangeSeq
)
260 [](const nsTArray
<RefPtr
<nsIWifiAccessPoint
>>& aAccessPoints
) {
261 EXPECT_TRUE(NS_IsMainThread());
262 EXPECT_EQ(aAccessPoints
.Length(), 1u);
268 EXPECT_CALL(*mWifiListener
, OnError
).Times(0).InSequence(aSeqs
.mOnErrorSeq
);
271 void AddStartWatchingCheck(bool aShouldPoll
, MockCallSequences
& aSeqs
) {
272 AddMockObjectChecks(aShouldPoll
? AtLeast(1) : Exactly(1), aSeqs
);
275 void AddNetworkEventCheck(const Cardinality
& aScanCardinality
,
276 MockCallSequences
& aSeqs
) {
277 AddMockObjectChecks(aScanCardinality
, aSeqs
);
280 void AddStopWatchingCheck(bool aShouldPoll
, MockCallSequences
& aSeqs
) {
281 // When polling, we may get stray scan + OnChange calls asynchronously
282 // before stopping. We may also get scan calls after stopping.
283 // We check that the calls actually stopped in ConfirmStoppedCheck.
284 AddMockObjectChecks(aShouldPoll
? AtLeast(0) : Exactly(0), aSeqs
);
287 void AddConfirmStoppedCheck(MockCallSequences
& aSeqs
) {
288 AddMockObjectChecks(Exactly(0), aSeqs
);
291 // A Checkpoint is just a mocked function taking an int. It will serve
292 // as a temporal barrier that requires all expectations before it to be
293 // satisfied and retired (meaning they won't be used in matches anymore).
296 void Check(uint32_t aId
, MockCallSequences
& aSeqs
) {
297 EXPECT_CALL(mFn
, Call(aId
))
298 .InSequence(aSeqs
.mGetAccessPointsSeq
, aSeqs
.mOnChangeSeq
,
302 void Reach(uint32_t aId
) { mFn
.Call(aId
); }
305 MockFunction
<void(uint32_t)> mFn
;
308 // A single test is StartWatching, NotifyOfNetworkEvent, and StopWatching.
309 void RunSingleTest(bool aRequestPolling
, bool aShouldPoll
,
310 const Cardinality
& aScanCardinality
, const char* aTopic
,
311 const char16_t
* aData
) {
312 LOGI(("RunSingleTest: <%s, %s> | requestPolling: %s | shouldPoll: %s",
313 aTopic
, NS_ConvertUTF16toUTF8(aData
).get(),
314 aRequestPolling
? "true" : "false", aShouldPoll
? "true" : "false"));
315 MOZ_RELEASE_ASSERT(aShouldPoll
|| !aRequestPolling
);
319 Checkpoint checkpoint
;
321 // gmock expectations are asynchronous by default. Sequence objects
322 // are used here to require that expectations occur in the specified
324 MockCallSequences seqs
;
326 AddStartWatchingCheck(aShouldPoll
, seqs
);
327 checkpoint
.Check(1, seqs
);
329 AddNetworkEventCheck(aScanCardinality
, seqs
);
330 checkpoint
.Check(2, seqs
);
332 AddStopWatchingCheck(aShouldPoll
, seqs
);
333 checkpoint
.Check(3, seqs
);
335 AddConfirmStoppedCheck(seqs
);
338 // Now run the test on the mock objects.
339 StartWatching(aRequestPolling
);
341 EXPECT_EQ(mWifiMonitor
->IsPolling(), aRequestPolling
);
343 NotifyOfNetworkEvent(aTopic
, aData
);
345 EXPECT_EQ(mWifiMonitor
->IsPolling(), aShouldPoll
);
349 EXPECT_TRUE(!mWifiMonitor
->IsPolling());
351 // Wait for extraneous calls as a way to confirm it has stopped.
352 WaitForScanResults();
357 void CheckMessages(bool aRequestPolling
) {
358 // NS_NETWORK_LINK_TOPIC messages should cause a new scan.
359 const char* kLinkTopicDatas
[] = {
360 NS_NETWORK_LINK_DATA_UP
, NS_NETWORK_LINK_DATA_DOWN
,
361 NS_NETWORK_LINK_DATA_CHANGED
, NS_NETWORK_LINK_DATA_UNKNOWN
};
363 for (const auto& data
: kLinkTopicDatas
) {
364 RunSingleTest(aRequestPolling
, aRequestPolling
,
365 aRequestPolling
? AtLeast(2) : Exactly(1),
366 NS_NETWORK_LINK_TOPIC
, NS_ConvertUTF8toUTF16(data
).get());
369 // NS_NETWORK_LINK_TYPE_TOPIC should cause wifi scan polling iff the topic
370 // says we have switched to a mobile network (LINK_TYPE_MOBILE or
371 // LINK_TYPE_WIMAX) or we are polling the wifi-scanner (aShouldPoll).
372 const LinkTypeMobility kLinkTypeTopicDatas
[] = {
373 {NS_NETWORK_LINK_TYPE_UNKNOWN
, true /* mIsMobile */},
374 {NS_NETWORK_LINK_TYPE_ETHERNET
, false},
375 {NS_NETWORK_LINK_TYPE_USB
, false},
376 {NS_NETWORK_LINK_TYPE_WIFI
, false},
377 {NS_NETWORK_LINK_TYPE_WIMAX
, true},
378 {NS_NETWORK_LINK_TYPE_MOBILE
, true}};
380 for (const auto& data
: kLinkTypeTopicDatas
) {
381 bool shouldPoll
= (aRequestPolling
|| data
.mIsMobile
);
382 RunSingleTest(aRequestPolling
, shouldPoll
,
383 shouldPoll
? AtLeast(2) : Exactly(0),
384 NS_NETWORK_LINK_TYPE_TOPIC
,
385 NS_ConvertUTF8toUTF16(data
.mLinkType
).get());
389 RefPtr
<nsWifiMonitor
> mWifiMonitor
;
390 nsCOMPtr
<nsIObserverService
> mObs
;
392 RefPtr
<MockWifiListener
> mWifiListener
;
394 int mOldScanInterval
;
395 uint32_t mOrigLinkType
= 0;
396 bool mOrigIsLinkUp
= false;
397 bool mOrigLinkStatusKnown
= false;
400 TEST_F(TestWifiMonitor
, WifiScanNoPolling
) { CheckMessages(false); }
402 TEST_F(TestWifiMonitor
, WifiScanPolling
) { CheckMessages(true); }
404 } // namespace mozilla