| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/cellular/cellular_capability_gsm.h" |
| |
| #include <base/bind.h> |
| #include <chromeos/dbus/service_constants.h> |
| #include <mm/mm-modem.h> |
| |
| #include "shill/cellular/cellular.h" |
| #include "shill/cellular/cellular_service.h" |
| #include "shill/cellular/mock_modem_cdma_proxy.h" |
| #include "shill/cellular/mock_modem_gobi_proxy.h" |
| #include "shill/cellular/mock_modem_gsm_card_proxy.h" |
| #include "shill/cellular/mock_modem_gsm_network_proxy.h" |
| #include "shill/cellular/mock_modem_info.h" |
| #include "shill/cellular/mock_modem_proxy.h" |
| #include "shill/cellular/mock_modem_simple_proxy.h" |
| #include "shill/error.h" |
| #include "shill/mock_adaptors.h" |
| #include "shill/mock_control.h" |
| #include "shill/mock_profile.h" |
| #include "shill/net/mock_rtnl_handler.h" |
| #include "shill/test_event_dispatcher.h" |
| #include "shill/testing.h" |
| |
| using base::Bind; |
| using base::Unretained; |
| using std::string; |
| using testing::InSequence; |
| using testing::NiceMock; |
| using testing::_; |
| |
| namespace shill { |
| |
| class CellularCapabilityClassicTest |
| : public testing::TestWithParam<Cellular::Type> { |
| public: |
| CellularCapabilityClassicTest() |
| : control_interface_(this), |
| modem_info_(&control_interface_, &dispatcher_, nullptr, nullptr), |
| create_gsm_card_proxy_from_factory_(false), |
| proxy_(new MockModemProxy()), |
| simple_proxy_(new MockModemSimpleProxy()), |
| cdma_proxy_(new MockModemCdmaProxy()), |
| gsm_card_proxy_(new MockModemGsmCardProxy()), |
| gsm_network_proxy_(new MockModemGsmNetworkProxy()), |
| gobi_proxy_(new MockModemGobiProxy()), |
| capability_(nullptr), |
| device_adaptor_(nullptr), |
| cellular_(new Cellular(&modem_info_, |
| "", |
| "", |
| 0, |
| GetParam(), |
| "", |
| "")) { |
| modem_info_.metrics()->RegisterDevice(cellular_->interface_index(), |
| Technology::kCellular); |
| } |
| |
| ~CellularCapabilityClassicTest() override { |
| cellular_->service_ = nullptr; |
| capability_ = nullptr; |
| device_adaptor_ = nullptr; |
| } |
| |
| void SetUp() override { |
| static_cast<Device*>(cellular_.get())->rtnl_handler_ = &rtnl_handler_; |
| |
| capability_ = static_cast<CellularCapabilityClassic*>( |
| cellular_->capability_.get()); |
| device_adaptor_ = |
| static_cast<DeviceMockAdaptor*>(cellular_->adaptor()); |
| ASSERT_NE(nullptr, device_adaptor_);; |
| } |
| |
| // TODO(benchan): Instead of conditionally enabling many tests for specific |
| // capability types via IsCellularTypeUnderTestOneOf, migrate more tests to |
| // work under all capability types and proboably migrate those tests for |
| // specific capability types into their own test fixture subclasses. |
| bool IsCellularTypeUnderTestOneOf( |
| const std::set<Cellular::Type>& valid_types) const { |
| return base::ContainsKey(valid_types, GetParam()); |
| } |
| |
| void CreateService() { |
| // The following constants are never directly accessed by the tests. |
| const char kFriendlyServiceName[] = "default_test_service_name"; |
| const char kOperatorCode[] = "10010"; |
| const char kOperatorName[] = "default_test_operator_name"; |
| const char kOperatorCountry[] = "us"; |
| |
| // Simulate all the side-effects of Cellular::CreateService |
| auto service = new CellularService(&modem_info_, cellular_); |
| service->SetFriendlyName(kFriendlyServiceName); |
| |
| Stringmap serving_operator; |
| serving_operator[kOperatorCodeKey] = kOperatorCode; |
| serving_operator[kOperatorNameKey] = kOperatorName; |
| serving_operator[kOperatorCountryKey] = kOperatorCountry; |
| |
| service->set_serving_operator(serving_operator); |
| cellular_->set_home_provider(serving_operator); |
| cellular_->service_ = service; |
| } |
| |
| CellularCapabilityGsm* GetGsmCapability() { |
| return static_cast<CellularCapabilityGsm*>(cellular_->capability_.get()); |
| } |
| |
| void ReleaseCapabilityProxies() { |
| capability_->ReleaseProxies(); |
| } |
| |
| void InvokeEnable(bool enable, Error* error, |
| const ResultCallback& callback, int timeout) { |
| callback.Run(Error()); |
| } |
| void InvokeEnableFail(bool enable, Error* error, |
| const ResultCallback& callback, int timeout) { |
| callback.Run(Error(Error::kOperationFailed)); |
| } |
| void InvokeDisconnect(Error* error, const ResultCallback& callback, |
| int timeout) { |
| callback.Run(Error()); |
| } |
| void InvokeDisconnectFail(Error* error, const ResultCallback& callback, |
| int timeout) { |
| callback.Run(Error(Error::kOperationFailed)); |
| } |
| void InvokeGetModemStatus(Error* error, |
| const KeyValueStoreCallback& callback, |
| int timeout) { |
| KeyValueStore props; |
| props.SetString("carrier", kTestCarrier); |
| props.SetString("unknown-property", "irrelevant-value"); |
| callback.Run(props, Error()); |
| } |
| void InvokeGetModemInfo(Error* error, const ModemInfoCallback& callback, |
| int timeout) { |
| callback.Run(kManufacturer, kModelID, kHWRev, Error()); |
| } |
| void InvokeSetCarrier(const string& carrier, Error* error, |
| const ResultCallback& callback, int timeout) { |
| callback.Run(Error()); |
| } |
| |
| MOCK_METHOD1(TestCallback, void(const Error& error)); |
| |
| protected: |
| static const char kTestMobileProviderDBPath[]; |
| static const char kTestCarrier[]; |
| static const char kManufacturer[]; |
| static const char kModelID[]; |
| static const char kHWRev[]; |
| |
| class TestControl : public MockControl { |
| public: |
| explicit TestControl(CellularCapabilityClassicTest* test) : test_(test) {} |
| |
| std::unique_ptr<ModemProxyInterface> CreateModemProxy( |
| const string& /*path*/, |
| const string& /*service*/) override { |
| return std::move(test_->proxy_); |
| } |
| |
| std::unique_ptr<ModemSimpleProxyInterface> CreateModemSimpleProxy( |
| const string& /*path*/, |
| const string& /*service*/) override { |
| return std::move(test_->simple_proxy_); |
| } |
| |
| std::unique_ptr<ModemCdmaProxyInterface> CreateModemCdmaProxy( |
| const string& /*path*/, |
| const string& /*service*/) override { |
| return std::move(test_->cdma_proxy_); |
| } |
| |
| std::unique_ptr<ModemGsmCardProxyInterface> CreateModemGsmCardProxy( |
| const string& /*path*/, |
| const string& /*service*/) override { |
| // TODO(benchan): This code conditionally returns a nullptr to avoid |
| // CellularCapabilityGsm::InitProperties (and thus |
| // CellularCapabilityGsm::GetIMSI) from being called during the |
| // construction. Remove this workaround after refactoring the tests. |
| if (test_->create_gsm_card_proxy_from_factory_) { |
| return std::move(test_->gsm_card_proxy_); |
| } |
| return nullptr; |
| } |
| |
| std::unique_ptr<ModemGsmNetworkProxyInterface> CreateModemGsmNetworkProxy( |
| const string& /*path*/, const string& /*service*/) override { |
| return std::move(test_->gsm_network_proxy_); |
| } |
| |
| std::unique_ptr<ModemGobiProxyInterface> CreateModemGobiProxy( |
| const string& /*path*/, |
| const string& /*service*/) override { |
| return std::move(test_->gobi_proxy_); |
| } |
| |
| private: |
| CellularCapabilityClassicTest* test_; |
| }; |
| |
| void SetProxy() { |
| capability_->proxy_ = std::move(proxy_); |
| } |
| |
| void SetSimpleProxy() { |
| capability_->simple_proxy_ = std::move(simple_proxy_); |
| } |
| |
| void SetGsmNetworkProxy() { |
| CellularCapabilityGsm* gsm_capability = |
| static_cast<CellularCapabilityGsm*>(cellular_->capability_.get()); |
| gsm_capability->network_proxy_ = std::move(gsm_network_proxy_); |
| } |
| |
| void AllowCreateGsmCardProxyFromFactory() { |
| create_gsm_card_proxy_from_factory_ = true; |
| } |
| |
| EventDispatcherForTest dispatcher_; |
| TestControl control_interface_; |
| MockModemInfo modem_info_; |
| MockRTNLHandler rtnl_handler_; |
| bool create_gsm_card_proxy_from_factory_; |
| std::unique_ptr<MockModemProxy> proxy_; |
| std::unique_ptr<MockModemSimpleProxy> simple_proxy_; |
| std::unique_ptr<MockModemCdmaProxy> cdma_proxy_; |
| std::unique_ptr<MockModemGsmCardProxy> gsm_card_proxy_; |
| std::unique_ptr<MockModemGsmNetworkProxy> gsm_network_proxy_; |
| std::unique_ptr<MockModemGobiProxy> gobi_proxy_; |
| CellularCapabilityClassic* capability_; // Owned by |cellular_|. |
| DeviceMockAdaptor* device_adaptor_; // Owned by |cellular_|. |
| CellularRefPtr cellular_; |
| }; |
| |
| const char CellularCapabilityClassicTest::kTestMobileProviderDBPath[] = |
| "provider_db_unittest.bfd"; |
| const char CellularCapabilityClassicTest::kTestCarrier[] = |
| "The Cellular Carrier"; |
| const char CellularCapabilityClassicTest::kManufacturer[] = "Company"; |
| const char CellularCapabilityClassicTest::kModelID[] = "Gobi 2000"; |
| const char CellularCapabilityClassicTest::kHWRev[] = "A00B1234"; |
| |
| TEST_P(CellularCapabilityClassicTest, GetModemStatus) { |
| EXPECT_CALL(*simple_proxy_, |
| GetModemStatus(_, _, CellularCapability::kTimeoutDefault)) |
| .WillOnce( |
| Invoke(this, &CellularCapabilityClassicTest::InvokeGetModemStatus)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| SetSimpleProxy(); |
| ResultCallback callback = |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)); |
| capability_->GetModemStatus(callback); |
| EXPECT_EQ(kTestCarrier, cellular_->carrier()); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, GetModemInfo) { |
| if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) { |
| return; |
| } |
| |
| EXPECT_CALL(*proxy_, GetModemInfo(_, _, CellularCapability::kTimeoutDefault)) |
| .WillOnce( |
| Invoke(this, &CellularCapabilityClassicTest::InvokeGetModemInfo)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| SetProxy(); |
| ResultCallback callback = |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)); |
| capability_->GetModemInfo(callback); |
| EXPECT_EQ(kManufacturer, cellular_->manufacturer()); |
| EXPECT_EQ(kModelID, cellular_->model_id()); |
| EXPECT_EQ(kHWRev, cellular_->hardware_revision()); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, EnableModemSucceed) { |
| EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable)) |
| .WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeEnable)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| ResultCallback callback = |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)); |
| SetProxy(); |
| capability_->EnableModem(callback); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, EnableModemFail) { |
| EXPECT_CALL(*proxy_, Enable(true, _, _, CellularCapability::kTimeoutEnable)) |
| .WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeEnableFail)); |
| EXPECT_CALL(*this, TestCallback(IsFailure())); |
| ResultCallback callback = |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this)); |
| SetProxy(); |
| capability_->EnableModem(callback); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, FinishEnable) { |
| if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) { |
| return; |
| } |
| |
| EXPECT_CALL(*gsm_network_proxy_, |
| GetRegistrationInfo(nullptr, _, |
| CellularCapability::kTimeoutDefault)); |
| EXPECT_CALL( |
| *gsm_network_proxy_, |
| GetSignalQuality(nullptr, _, CellularCapability::kTimeoutDefault)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| SetGsmNetworkProxy(); |
| capability_->FinishEnable( |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this))); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, UnsupportedOperation) { |
| Error error; |
| EXPECT_CALL(*this, TestCallback(IsSuccess())).Times(0); |
| capability_->CellularCapability::Reset( |
| &error, |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this))); |
| EXPECT_TRUE(error.IsFailure()); |
| EXPECT_EQ(Error::kNotSupported, error.type()); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, AllowRoaming) { |
| if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) { |
| return; |
| } |
| |
| EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr)); |
| cellular_->SetAllowRoaming(false, nullptr); |
| EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr)); |
| |
| { |
| InSequence seq; |
| EXPECT_CALL(*device_adaptor_, |
| EmitBoolChanged(kCellularAllowRoamingProperty, true)); |
| EXPECT_CALL(*device_adaptor_, |
| EmitBoolChanged(kCellularAllowRoamingProperty, false)); |
| } |
| |
| cellular_->state_ = Cellular::kStateConnected; |
| static_cast<CellularCapabilityGsm*>(capability_)->registration_state_ = |
| MM_MODEM_GSM_NETWORK_REG_STATUS_ROAMING; |
| cellular_->SetAllowRoaming(true, nullptr); |
| EXPECT_TRUE(cellular_->GetAllowRoaming(nullptr)); |
| EXPECT_EQ(Cellular::kStateConnected, cellular_->state_); |
| |
| EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect)) |
| .WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeDisconnect)); |
| SetProxy(); |
| cellular_->state_ = Cellular::kStateConnected; |
| cellular_->SetAllowRoaming(false, nullptr); |
| EXPECT_FALSE(cellular_->GetAllowRoaming(nullptr)); |
| EXPECT_EQ(Cellular::kStateRegistered, cellular_->state_); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, SetCarrier) { |
| static const char kCarrier[] = "Generic UMTS"; |
| EXPECT_CALL( |
| *gobi_proxy_, |
| SetCarrier(kCarrier, _, _, |
| CellularCapabilityClassic::kTimeoutSetCarrierMilliseconds)) |
| .WillOnce(Invoke(this, &CellularCapabilityClassicTest::InvokeSetCarrier)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| Error error; |
| capability_->SetCarrier(kCarrier, &error, |
| Bind(&CellularCapabilityClassicTest::TestCallback, |
| Unretained(this))); |
| EXPECT_TRUE(error.IsSuccess()); |
| } |
| |
| MATCHER_P(HasApn, apn, "") { |
| return arg.ContainsString(kApnProperty) && apn == arg.GetString(kApnProperty); |
| } |
| |
| MATCHER(HasNoApn, "") { |
| return !arg.ContainsString(kApnProperty); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, TryApns) { |
| if (!IsCellularTypeUnderTestOneOf({Cellular::kTypeGsm})) { |
| return; |
| } |
| |
| static const string kLastGoodApn("remembered.apn"); |
| static const string kLastGoodUsername("remembered.user"); |
| static const string kSuppliedApn("my.apn"); |
| static const string kTmobileApn1("epc.tmobile.com"); |
| static const string kTmobileApn2("wap.voicestream.com"); |
| static const string kTmobileApn3("internet2.voicestream.com"); |
| static const string kTmobileApn4("internet3.voicestream.com"); |
| const Stringmaps kDatabaseApnList {{{ kApnProperty, kTmobileApn1 }}, |
| {{ kApnProperty, kTmobileApn2 }}, |
| {{ kApnProperty, kTmobileApn3 }}, |
| {{ kApnProperty, kTmobileApn4 }}}; |
| |
| |
| CreateService(); |
| // Supply the database APNs to |cellular_| object. |
| cellular_->set_apn_list(kDatabaseApnList); |
| ProfileRefPtr profile(new NiceMock<MockProfile>( |
| modem_info_.control_interface(), modem_info_.metrics(), |
| modem_info_.manager())); |
| cellular_->service()->set_profile(profile); |
| |
| Error error; |
| Stringmap apn_info; |
| KeyValueStore props; |
| CellularCapabilityGsm* gsm_capability = GetGsmCapability(); |
| |
| apn_info[kApnProperty] = kLastGoodApn; |
| apn_info[kApnUsernameProperty] = kLastGoodUsername; |
| cellular_->service()->SetLastGoodApn(apn_info); |
| props.Clear(); |
| EXPECT_TRUE(props.IsEmpty()); |
| gsm_capability->SetupConnectProperties(&props); |
| // We expect the list to contain the last good APN, plus |
| // the 4 APNs from the mobile provider info database. |
| EXPECT_EQ(5, gsm_capability->apn_try_list_.size()); |
| EXPECT_TRUE(props.ContainsString(kApnProperty)); |
| EXPECT_EQ(kLastGoodApn, props.GetString(kApnProperty)); |
| EXPECT_TRUE(props.ContainsString(kApnUsernameProperty)); |
| EXPECT_EQ(kLastGoodUsername, |
| props.GetString(kApnUsernameProperty)); |
| |
| apn_info.clear(); |
| props.Clear(); |
| apn_info[kApnProperty] = kSuppliedApn; |
| // After setting the APN, the last good APN is perserved. We expect the try |
| // list to contain the user-supplied APN, the last good APN, and the 4 APNs |
| // from the mobile provider info database. The user-supplied APN will be |
| // selected before the last good APN. |
| cellular_->service()->SetApn(apn_info, &error); |
| EXPECT_TRUE(props.IsEmpty()); |
| gsm_capability->SetupConnectProperties(&props); |
| EXPECT_EQ(6, gsm_capability->apn_try_list_.size()); |
| EXPECT_TRUE(props.ContainsString(kApnProperty)); |
| EXPECT_EQ(kSuppliedApn, props.GetString(kApnProperty)); |
| |
| Stringmap* last_good_apn = cellular_->service()->GetLastGoodApn(); |
| ASSERT_NE(nullptr, last_good_apn); |
| EXPECT_EQ(kLastGoodApn, (*last_good_apn)[kApnProperty]); |
| EXPECT_EQ(kLastGoodUsername, (*last_good_apn)[kApnUsernameProperty]); |
| |
| // Now try all the given APNs. |
| using testing::InSequence; |
| { |
| InSequence dummy; |
| EXPECT_CALL(*simple_proxy_, Connect(HasApn(kSuppliedApn), _, _, _)); |
| EXPECT_CALL(*simple_proxy_, Connect(HasApn(kLastGoodApn), _, _, _)); |
| EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn1), _, _, _)); |
| EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn2), _, _, _)); |
| EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn3), _, _, _)); |
| EXPECT_CALL(*simple_proxy_, Connect(HasApn(kTmobileApn4), _, _, _)); |
| EXPECT_CALL(*simple_proxy_, Connect(HasNoApn(), _, _, _)); |
| } |
| SetSimpleProxy(); |
| gsm_capability->Connect(props, &error, ResultCallback()); |
| Error cerror(Error::kInvalidApn); |
| gsm_capability->OnConnectReply(ResultCallback(), cerror); |
| EXPECT_EQ(5, gsm_capability->apn_try_list_.size()); |
| gsm_capability->OnConnectReply(ResultCallback(), cerror); |
| EXPECT_EQ(4, gsm_capability->apn_try_list_.size()); |
| gsm_capability->OnConnectReply(ResultCallback(), cerror); |
| EXPECT_EQ(3, gsm_capability->apn_try_list_.size()); |
| gsm_capability->OnConnectReply(ResultCallback(), cerror); |
| EXPECT_EQ(2, gsm_capability->apn_try_list_.size()); |
| gsm_capability->OnConnectReply(ResultCallback(), cerror); |
| EXPECT_EQ(1, gsm_capability->apn_try_list_.size()); |
| gsm_capability->OnConnectReply(ResultCallback(), cerror); |
| EXPECT_EQ(0, gsm_capability->apn_try_list_.size()); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, StopModemDisconnectSuccess) { |
| EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect)) |
| .WillOnce(Invoke(this, |
| &CellularCapabilityClassicTest::InvokeDisconnect)); |
| EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable)) |
| .WillOnce(Invoke(this, |
| &CellularCapabilityClassicTest::InvokeEnable)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| SetProxy(); |
| |
| Error error; |
| capability_->StopModem( |
| &error, |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this))); |
| dispatcher_.DispatchPendingEvents(); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, StopModemDisconnectFail) { |
| EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect)) |
| .WillOnce(Invoke(this, |
| &CellularCapabilityClassicTest::InvokeDisconnectFail)); |
| EXPECT_CALL(*proxy_, Enable(_, _, _, CellularCapability::kTimeoutEnable)) |
| .WillOnce(Invoke(this, |
| &CellularCapabilityClassicTest::InvokeEnable)); |
| EXPECT_CALL(*this, TestCallback(IsSuccess())); |
| SetProxy(); |
| |
| Error error; |
| capability_->StopModem( |
| &error, |
| Bind(&CellularCapabilityClassicTest::TestCallback, Unretained(this))); |
| dispatcher_.DispatchPendingEvents(); |
| } |
| |
| TEST_P(CellularCapabilityClassicTest, DisconnectNoProxy) { |
| Error error; |
| ResultCallback disconnect_callback; |
| EXPECT_CALL(*proxy_, Disconnect(_, _, CellularCapability::kTimeoutDisconnect)) |
| .Times(0); |
| ReleaseCapabilityProxies(); |
| capability_->Disconnect(&error, disconnect_callback); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(CellularCapabilityClassicTest, |
| CellularCapabilityClassicTest, |
| testing::Values(Cellular::kTypeGsm, |
| Cellular::kTypeCdma)); |
| |
| } // namespace shill |