TestWifiMonitor.cpp (14219B)
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/. */ 6 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" 13 #include "nsCOMPtr.h" 14 #include "mozilla/Preferences.h" 15 #include "mozilla/Services.h" 16 #include "nsIObserverService.h" 17 #include "nsINetworkLinkService.h" 18 #include "mozilla/SpinEventLoopUntil.h" 19 #include "nsNetCID.h" 20 #include "nsServiceManagerUtils.h" 21 22 #if defined(XP_WIN) && defined(_M_IX86) 23 # include <objbase.h> // STDMETHODCALLTYPE 24 #endif 25 26 // Tests that wifi scanning happens on the right network change events, 27 // and that wifi-scan polling is operable on mobile networks. 28 29 using ::testing::AtLeast; 30 using ::testing::Cardinality; 31 using ::testing::Exactly; 32 using ::testing::MockFunction; 33 using ::testing::Sequence; 34 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) 38 39 namespace mozilla { 40 41 // Timeout if update not received from wifi scanner thread. 42 static const uint32_t kWifiScanTestResultTimeoutMs = 100; 43 static const uint32_t kTestWifiScanIntervalMs = 10; 44 45 // ID counter, used to make sure each call to GetAccessPointsFromWLAN 46 // returns "new" access points. 47 static int gCurrentId = 0; 48 49 static uint32_t gNumScanResults = 0; 50 51 struct LinkTypeMobility { 52 const char* mLinkType; 53 bool mIsMobile; 54 }; 55 56 class MockWifiScanner : public WifiScanner { 57 public: 58 MOCK_METHOD(nsresult, GetAccessPointsFromWLAN, 59 (nsTArray<RefPtr<nsIWifiAccessPoint>> & aAccessPoints), 60 (override)); 61 }; 62 63 class MockWifiListener : public nsIWifiListener { 64 virtual ~MockWifiListener() = default; 65 66 public: 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))); 74 #else 75 MOCK_METHOD(nsresult, OnChange, 76 (const nsTArray<RefPtr<nsIWifiAccessPoint>>& accessPoints), 77 (override)); 78 MOCK_METHOD(nsresult, OnError, (nsresult error), (override)); 79 #endif 80 }; 81 82 NS_IMPL_ISUPPORTS(MockWifiListener, nsIWifiListener) 83 84 class TestWifiMonitor : public ::testing::Test { 85 public: 86 TestWifiMonitor() { 87 mObs = mozilla::services::GetObserverService(); 88 MOZ_RELEASE_ASSERT(mObs); 89 90 nsresult rv; 91 nsCOMPtr<nsINetworkLinkService> nls = 92 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv); 93 EXPECT_TRUE(NS_SUCCEEDED(rv)); 94 EXPECT_TRUE(nls); 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)); 101 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); 105 } 106 107 ~TestWifiMonitor() { 108 Preferences::SetInt(WIFI_SCAN_INTERVAL_MS_PREF, mOldScanInterval); 109 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; 115 break; 116 case nsINetworkLinkService::LINK_TYPE_ETHERNET: 117 linkType = NS_NETWORK_LINK_TYPE_ETHERNET; 118 break; 119 case nsINetworkLinkService::LINK_TYPE_USB: 120 linkType = NS_NETWORK_LINK_TYPE_USB; 121 break; 122 case nsINetworkLinkService::LINK_TYPE_WIFI: 123 linkType = NS_NETWORK_LINK_TYPE_WIFI; 124 break; 125 case nsINetworkLinkService::LINK_TYPE_MOBILE: 126 linkType = NS_NETWORK_LINK_TYPE_MOBILE; 127 break; 128 case nsINetworkLinkService::LINK_TYPE_WIMAX: 129 linkType = NS_NETWORK_LINK_TYPE_WIMAX; 130 break; 131 } 132 EXPECT_TRUE(linkType); 133 mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TYPE_TOPIC, 134 NS_ConvertUTF8toUTF16(linkType).get()); 135 136 const char* linkStatus = nullptr; 137 if (mOrigLinkStatusKnown) { 138 if (mOrigIsLinkUp) { 139 linkStatus = NS_NETWORK_LINK_DATA_UP; 140 } else { 141 linkStatus = NS_NETWORK_LINK_DATA_DOWN; 142 } 143 } else { 144 linkStatus = NS_NETWORK_LINK_DATA_UNKNOWN; 145 } 146 EXPECT_TRUE(linkStatus); 147 mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC, 148 NS_ConvertUTF8toUTF16(linkStatus).get()); 149 } 150 151 protected: 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); 159 160 mozilla::SpinEventLoopUntil("TestWifiMonitor::WaitForScanResults"_ns, 161 [&]() { return timedout; }); 162 163 timer->Cancel(); 164 return true; 165 } 166 167 void CreateObjects() { 168 mWifiMonitor = MakeRefPtr<nsWifiMonitor>(MakeUnique<MockWifiScanner>()); 169 EXPECT_TRUE(!mWifiMonitor->IsPolling()); 170 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()); 175 176 mWifiListener = new MockWifiListener(); 177 LOGI(("monitor: %p | scanner: %p | listener: %p", mWifiMonitor.get(), 178 mWifiMonitor->mWifiScanner.get(), mWifiListener.get())); 179 } 180 181 void DestroyObjects() { 182 ::testing::Mock::VerifyAndClearExpectations( 183 mWifiMonitor->mWifiScanner.get()); 184 ::testing::Mock::VerifyAndClearExpectations(mWifiListener.get()); 185 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 189 // error. 190 mWifiMonitor->Close(); 191 192 mWifiMonitor = nullptr; 193 mWifiListener = nullptr; 194 gCurrentId = 0; 195 } 196 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(); 203 } 204 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(); 210 } 211 212 void StopWatching() { 213 LOGD(("StopWatching | nScanResults: %u", gNumScanResults)); 214 EXPECT_TRUE(NS_SUCCEEDED(mWifiMonitor->StopWatching(mWifiListener))); 215 WaitForScanResults(); 216 } 217 218 struct MockCallSequences { 219 Sequence mGetAccessPointsSeq; 220 Sequence mOnChangeSeq; 221 Sequence mOnErrorSeq; 222 }; 223 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)) { 229 EXPECT_CALL( 230 *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()), 231 GetAccessPointsFromWLAN) 232 .Times(aScanCardinality) 233 .InSequence(aSeqs.mGetAccessPointsSeq); 234 235 EXPECT_CALL(*mWifiListener, OnChange) 236 .Times(aScanCardinality) 237 .InSequence(aSeqs.mOnChangeSeq); 238 } else { 239 EXPECT_CALL( 240 *static_cast<MockWifiScanner*>(mWifiMonitor->mWifiScanner.get()), 241 GetAccessPointsFromWLAN) 242 .Times(aScanCardinality) 243 .InSequence(aSeqs.mGetAccessPointsSeq) 244 .WillRepeatedly( 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 250 // point list. 251 ap->mSignal = gCurrentId++; 252 aAccessPoints.AppendElement(RefPtr(ap)); 253 return NS_OK; 254 }); 255 256 EXPECT_CALL(*mWifiListener, OnChange) 257 .Times(aScanCardinality) 258 .InSequence(aSeqs.mOnChangeSeq) 259 .WillRepeatedly( 260 [](const nsTArray<RefPtr<nsIWifiAccessPoint>>& aAccessPoints) { 261 EXPECT_TRUE(NS_IsMainThread()); 262 EXPECT_EQ(aAccessPoints.Length(), 1u); 263 ++gNumScanResults; 264 return NS_OK; 265 }); 266 } 267 268 EXPECT_CALL(*mWifiListener, OnError).Times(0).InSequence(aSeqs.mOnErrorSeq); 269 } 270 271 void AddStartWatchingCheck(bool aShouldPoll, MockCallSequences& aSeqs) { 272 AddMockObjectChecks(aShouldPoll ? AtLeast(1) : Exactly(1), aSeqs); 273 } 274 275 void AddNetworkEventCheck(const Cardinality& aScanCardinality, 276 MockCallSequences& aSeqs) { 277 AddMockObjectChecks(aScanCardinality, aSeqs); 278 } 279 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); 285 } 286 287 void AddConfirmStoppedCheck(MockCallSequences& aSeqs) { 288 AddMockObjectChecks(Exactly(0), aSeqs); 289 } 290 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). 294 class Checkpoint { 295 public: 296 void Check(uint32_t aId, MockCallSequences& aSeqs) { 297 EXPECT_CALL(mFn, Call(aId)) 298 .InSequence(aSeqs.mGetAccessPointsSeq, aSeqs.mOnChangeSeq, 299 aSeqs.mOnErrorSeq); 300 } 301 302 void Reach(uint32_t aId) { mFn.Call(aId); } 303 304 private: 305 MockFunction<void(uint32_t)> mFn; 306 }; 307 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); 316 317 CreateObjects(); 318 319 Checkpoint checkpoint; 320 { 321 // gmock expectations are asynchronous by default. Sequence objects 322 // are used here to require that expectations occur in the specified 323 // (partial) order. 324 MockCallSequences seqs; 325 326 AddStartWatchingCheck(aShouldPoll, seqs); 327 checkpoint.Check(1, seqs); 328 329 AddNetworkEventCheck(aScanCardinality, seqs); 330 checkpoint.Check(2, seqs); 331 332 AddStopWatchingCheck(aShouldPoll, seqs); 333 checkpoint.Check(3, seqs); 334 335 AddConfirmStoppedCheck(seqs); 336 } 337 338 // Now run the test on the mock objects. 339 StartWatching(aRequestPolling); 340 checkpoint.Reach(1); 341 EXPECT_EQ(mWifiMonitor->IsPolling(), aRequestPolling); 342 343 NotifyOfNetworkEvent(aTopic, aData); 344 checkpoint.Reach(2); 345 EXPECT_EQ(mWifiMonitor->IsPolling(), aShouldPoll); 346 347 StopWatching(); 348 checkpoint.Reach(3); 349 EXPECT_TRUE(!mWifiMonitor->IsPolling()); 350 351 // Wait for extraneous calls as a way to confirm it has stopped. 352 WaitForScanResults(); 353 354 DestroyObjects(); 355 } 356 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}; 362 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()); 367 } 368 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}}; 379 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()); 386 } 387 } 388 389 RefPtr<nsWifiMonitor> mWifiMonitor; 390 nsCOMPtr<nsIObserverService> mObs; 391 392 RefPtr<MockWifiListener> mWifiListener; 393 394 int mOldScanInterval; 395 uint32_t mOrigLinkType = 0; 396 bool mOrigIsLinkUp = false; 397 bool mOrigLinkStatusKnown = false; 398 }; 399 400 TEST_F(TestWifiMonitor, WifiScanNoPolling) { CheckMessages(false); } 401 402 TEST_F(TestWifiMonitor, WifiScanPolling) { CheckMessages(true); } 403 404 } // namespace mozilla