Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / netwerk / wifi / gtest / TestWifiMonitor.cpp
blob37d46c658ab03a4c8c20927b95c958e79fdec287
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"
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"
22 #if defined(XP_WIN) && defined(_M_IX86)
23 # include <objbase.h> // STDMETHODCALLTYPE
24 #endif
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)
39 namespace mozilla {
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;
53 bool mIsMobile;
56 class MockWifiScanner : public WifiScanner {
57 public:
58 MOCK_METHOD(nsresult, GetAccessPointsFromWLAN,
59 (nsTArray<RefPtr<nsIWifiAccessPoint>> & aAccessPoints),
60 (override));
63 class MockWifiListener : public nsIWifiListener {
64 virtual ~MockWifiListener() = default;
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
82 NS_IMPL_ISUPPORTS(MockWifiListener, nsIWifiListener)
84 class TestWifiMonitor : public ::testing::Test {
85 public:
86 TestWifiMonitor() {
87 mObs = mozilla::services::GetObserverService();
88 MOZ_RELEASE_ASSERT(mObs);
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));
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);
107 ~TestWifiMonitor() {
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;
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;
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) {
138 if (mOrigIsLinkUp) {
139 linkStatus = NS_NETWORK_LINK_DATA_UP;
140 } else {
141 linkStatus = NS_NETWORK_LINK_DATA_DOWN;
143 } else {
144 linkStatus = NS_NETWORK_LINK_DATA_UNKNOWN;
146 EXPECT_TRUE(linkStatus);
147 mObs->NotifyObservers(nullptr, NS_NETWORK_LINK_TOPIC,
148 NS_ConvertUTF8toUTF16(linkStatus).get());
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);
160 mozilla::SpinEventLoopUntil("TestWifiMonitor::WaitForScanResults"_ns,
161 [&]() { return timedout; });
163 timer->Cancel();
164 return true;
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
189 // error.
190 mWifiMonitor->Close();
192 mWifiMonitor = nullptr;
193 mWifiListener = nullptr;
194 gCurrentId = 0;
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)) {
229 EXPECT_CALL(
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);
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;
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;
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).
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);
302 void Reach(uint32_t aId) { mFn.Call(aId); }
304 private:
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);
317 CreateObjects();
319 Checkpoint checkpoint;
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;
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);
340 checkpoint.Reach(1);
341 EXPECT_EQ(mWifiMonitor->IsPolling(), aRequestPolling);
343 NotifyOfNetworkEvent(aTopic, aData);
344 checkpoint.Reach(2);
345 EXPECT_EQ(mWifiMonitor->IsPolling(), aShouldPoll);
347 StopWatching();
348 checkpoint.Reach(3);
349 EXPECT_TRUE(!mWifiMonitor->IsPolling());
351 // Wait for extraneous calls as a way to confirm it has stopped.
352 WaitForScanResults();
354 DestroyObjects();
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