tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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