// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/profile.h" #include #include #include #include #include #include #include "shill/fake_store.h" #include "shill/mock_manager.h" #include "shill/mock_metrics.h" #include "shill/mock_profile.h" #include "shill/mock_service.h" #include "shill/mock_store.h" #include "shill/property_store_unittest.h" #include "shill/service_under_test.h" #include "shill/store_factory.h" using base::FilePath; using std::set; using std::string; using std::vector; using testing::_; using testing::Invoke; using testing::Mock; using testing::Return; using testing::SetArgumentPointee; using testing::StrictMock; namespace shill { class ProfileTest : public PropertyStoreTest { public: ProfileTest() : mock_metrics_(new MockMetrics(nullptr)) { Profile::Identifier id("rather", "irrelevant"); profile_ = new Profile( control_interface(), metrics(), manager(), id, FilePath(), false); // Install a FakeStore by default. In tests that actually care // about the interaction between Profile and StoreInterface, we'll // replace this with a MockStore. profile_->set_storage(new FakeStore()); } MockService* CreateMockService() { return new StrictMock(control_interface(), dispatcher(), metrics(), manager()); } bool ProfileInitStorage(const Profile::Identifier& id, Profile::InitStorageOption storage_option, bool save, Error::Type error_type) { // Note: this code uses neither FakeStore, nor MockStore. Instead, // it exercises a real StoreInterface implemenation. Error error; ProfileRefPtr profile( new Profile(control_interface(), mock_metrics_.get(), manager(), id, FilePath(storage_path()), false)); bool ret = profile->InitStorage(storage_option, &error); EXPECT_EQ(error_type, error.type()); if (ret && save) { EXPECT_TRUE(profile->Save()); } return ret; } protected: std::unique_ptr mock_metrics_; ProfileRefPtr profile_; }; TEST_F(ProfileTest, DeleteEntry) { std::unique_ptr manager(new StrictMock( control_interface(), dispatcher(), metrics())); profile_->manager_ = manager.get(); MockStore* storage(new StrictMock()); profile_->storage_.reset(storage); // Passes ownership const string kEntryName("entry_name"); // If entry does not appear in storage, DeleteEntry() should return an error. EXPECT_CALL(*storage, ContainsGroup(kEntryName)) .WillOnce(Return(false)); { Error error; profile_->DeleteEntry(kEntryName, &error); EXPECT_EQ(Error::kNotFound, error.type()); } Mock::VerifyAndClearExpectations(storage); // If HandleProfileEntryDeletion() returns false, Profile should call // DeleteGroup() itself. EXPECT_CALL(*storage, ContainsGroup(kEntryName)) .WillOnce(Return(true)); EXPECT_CALL(*manager.get(), HandleProfileEntryDeletion(_, kEntryName)) .WillOnce(Return(false)); EXPECT_CALL(*storage, DeleteGroup(kEntryName)) .WillOnce(Return(true)); EXPECT_CALL(*storage, Flush()) .WillOnce(Return(true)); { Error error; profile_->DeleteEntry(kEntryName, &error); EXPECT_TRUE(error.IsSuccess()); } Mock::VerifyAndClearExpectations(storage); // If HandleProfileEntryDeletion() returns true, Profile should not call // DeleteGroup() itself. EXPECT_CALL(*storage, ContainsGroup(kEntryName)) .WillOnce(Return(true)); EXPECT_CALL(*manager.get(), HandleProfileEntryDeletion(_, kEntryName)) .WillOnce(Return(true)); EXPECT_CALL(*storage, DeleteGroup(kEntryName)) .Times(0); EXPECT_CALL(*storage, Flush()) .WillOnce(Return(true)); { Error error; profile_->DeleteEntry(kEntryName, &error); EXPECT_TRUE(error.IsSuccess()); } } TEST_F(ProfileTest, IsValidIdentifierToken) { EXPECT_FALSE(Profile::IsValidIdentifierToken("")); EXPECT_FALSE(Profile::IsValidIdentifierToken(" ")); EXPECT_FALSE(Profile::IsValidIdentifierToken("-")); EXPECT_FALSE(Profile::IsValidIdentifierToken("~")); EXPECT_FALSE(Profile::IsValidIdentifierToken("_")); EXPECT_TRUE(Profile::IsValidIdentifierToken("a")); EXPECT_TRUE(Profile::IsValidIdentifierToken("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); EXPECT_TRUE(Profile::IsValidIdentifierToken("abcdefghijklmnopqrstuvwxyz")); EXPECT_TRUE(Profile::IsValidIdentifierToken("0123456789")); } TEST_F(ProfileTest, ParseIdentifier) { Profile::Identifier identifier; EXPECT_FALSE(Profile::ParseIdentifier("", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~foo", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~/", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~bar/", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~/zoo", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~./moo", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~valid/?", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~no//no", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("~no~no", &identifier)); static const char kUser[] = "user"; static const char kIdentifier[] = "identifier"; EXPECT_TRUE(Profile::ParseIdentifier( base::StringPrintf("~%s/%s", kUser, kIdentifier), &identifier)); EXPECT_EQ(kUser, identifier.user); EXPECT_EQ(kIdentifier, identifier.identifier); EXPECT_FALSE(Profile::ParseIdentifier("!", &identifier)); EXPECT_FALSE(Profile::ParseIdentifier("/nope", &identifier)); static const char kIdentifier2[] = "something"; EXPECT_TRUE(Profile::ParseIdentifier(kIdentifier2, &identifier)); EXPECT_EQ("", identifier.user); EXPECT_EQ(kIdentifier2, identifier.identifier); } TEST_F(ProfileTest, IdentifierToString) { Profile::Identifier identifier; static const char kUser[] = "user"; static const char kIdentifier[] = "identifier"; identifier.user = kUser; identifier.identifier = kIdentifier; EXPECT_EQ(base::StringPrintf("~%s/%s", kUser, kIdentifier), Profile::IdentifierToString(identifier)); } TEST_F(ProfileTest, GetFriendlyName) { static const char kUser[] = "theUser"; static const char kIdentifier[] = "theIdentifier"; Profile::Identifier id(kIdentifier); ProfileRefPtr profile(new Profile( control_interface(), metrics(), manager(), id, FilePath(), false)); EXPECT_EQ(kIdentifier, profile->GetFriendlyName()); id.user = kUser; profile = new Profile( control_interface(), metrics(), manager(), id, FilePath(), false); EXPECT_EQ(string(kUser) + "/" + kIdentifier, profile->GetFriendlyName()); } TEST_F(ProfileTest, GetStoragePath) { static const char kUser[] = "chronos"; static const char kIdentifier[] = "someprofile"; static const char kDirectory[] = "/a/place/for/"; Profile::Identifier id(kIdentifier); ProfileRefPtr profile(new Profile( control_interface(), metrics(), manager(), id, FilePath(), false)); EXPECT_TRUE(profile->persistent_profile_path_.empty()); id.user = kUser; profile = new Profile( control_interface(), metrics(), manager(), id, FilePath(kDirectory), false); #if defined(ENABLE_JSON_STORE) EXPECT_EQ("/a/place/for/chronos/someprofile.profile.json", profile->persistent_profile_path_.value()); #else EXPECT_EQ("/a/place/for/chronos/someprofile.profile", profile->persistent_profile_path_.value()); #endif } TEST_F(ProfileTest, ServiceManagement) { scoped_refptr service1(CreateMockService()); scoped_refptr service2(CreateMockService()); EXPECT_CALL(*service1.get(), Save(_)) .WillRepeatedly(Invoke(service1.get(), &MockService::FauxSave)); EXPECT_CALL(*service2.get(), Save(_)) .WillRepeatedly(Invoke(service2.get(), &MockService::FauxSave)); ASSERT_TRUE(profile_->AdoptService(service1)); ASSERT_TRUE(profile_->AdoptService(service2)); // Ensure services are in the profile now. ASSERT_TRUE(profile_->ContainsService(service1)); ASSERT_TRUE(profile_->ContainsService(service2)); // Ensure we can't add them twice. ASSERT_FALSE(profile_->AdoptService(service1)); ASSERT_FALSE(profile_->AdoptService(service2)); // Ensure that we can abandon individually, and that doing so is idempotent. ASSERT_TRUE(profile_->AbandonService(service1)); ASSERT_FALSE(profile_->ContainsService(service1)); ASSERT_TRUE(profile_->AbandonService(service1)); ASSERT_TRUE(profile_->ContainsService(service2)); // Clean up. ASSERT_TRUE(profile_->AbandonService(service2)); ASSERT_FALSE(profile_->ContainsService(service1)); ASSERT_FALSE(profile_->ContainsService(service2)); } TEST_F(ProfileTest, ServiceConfigure) { ServiceRefPtr service1(new ServiceUnderTest(control_interface(), dispatcher(), metrics(), manager())); // Change priority from default. service1->SetPriority(service1->priority() + 1, nullptr); ASSERT_TRUE(profile_->AdoptService(service1)); ASSERT_TRUE(profile_->ContainsService(service1)); // Create new service; ask Profile to merge it with a known, matching, // service; ensure that settings from |service1| wind up in |service2|. ServiceRefPtr service2(new ServiceUnderTest(control_interface(), dispatcher(), metrics(), manager())); int32_t orig_priority = service2->priority(); ASSERT_TRUE(profile_->ConfigureService(service2)); ASSERT_EQ(service1->priority(), service2->priority()); ASSERT_NE(orig_priority, service2->priority()); // Clean up. ASSERT_TRUE(profile_->AbandonService(service1)); ASSERT_FALSE(profile_->ContainsService(service1)); ASSERT_FALSE(profile_->ContainsService(service2)); } TEST_F(ProfileTest, Save) { scoped_refptr service1(CreateMockService()); scoped_refptr service2(CreateMockService()); EXPECT_CALL(*service1.get(), Save(_)).WillOnce(Return(true)); EXPECT_CALL(*service2.get(), Save(_)).WillOnce(Return(true)); ASSERT_TRUE(profile_->AdoptService(service1)); ASSERT_TRUE(profile_->AdoptService(service2)); profile_->Save(); } TEST_F(ProfileTest, EntryEnumeration) { scoped_refptr service1(CreateMockService()); scoped_refptr service2(CreateMockService()); string service1_storage_name = Technology::NameFromIdentifier( Technology::kCellular) + "_1"; string service2_storage_name = Technology::NameFromIdentifier( Technology::kCellular) + "_2"; EXPECT_CALL(*service1.get(), Save(_)) .WillRepeatedly(Invoke(service1.get(), &MockService::FauxSave)); EXPECT_CALL(*service2.get(), Save(_)) .WillRepeatedly(Invoke(service2.get(), &MockService::FauxSave)); EXPECT_CALL(*service1.get(), GetStorageIdentifier()) .WillRepeatedly(Return(service1_storage_name)); EXPECT_CALL(*service2.get(), GetStorageIdentifier()) .WillRepeatedly(Return(service2_storage_name)); string service1_name(service1->unique_name()); string service2_name(service2->unique_name()); ASSERT_TRUE(profile_->AdoptService(service1)); ASSERT_TRUE(profile_->AdoptService(service2)); Error error; ASSERT_EQ(2, profile_->EnumerateEntries(&error).size()); ASSERT_TRUE(profile_->AbandonService(service1)); ASSERT_EQ(service2_storage_name, profile_->EnumerateEntries(&error)[0]); ASSERT_TRUE(profile_->AbandonService(service2)); ASSERT_EQ(0, profile_->EnumerateEntries(&error).size()); } TEST_F(ProfileTest, LoadUserProfileList) { FilePath list_path(FilePath(storage_path()).Append("test.profile")); vector identifiers = Profile::LoadUserProfileList(list_path); EXPECT_TRUE(identifiers.empty()); const char kUser0[] = "scarecrow"; const char kUser1[] = "jeans"; const char kIdentifier0[] = "rattlesnake"; const char kIdentifier1[] = "ceiling"; const char kHash0[] = "neighbors"; string data(base::StringPrintf("\n" "~userbut/nospacehere\n" "defaultprofile notaccepted\n" "~%s/%s %s\n" "~userbutno/hash\n" " ~dontaccept/leadingspaces hash\n" "~this_username_fails_to_parse/id hash\n" "~%s/%s \n\n", kUser0, kIdentifier0, kHash0, kUser1, kIdentifier1)); EXPECT_EQ(data.size(), base::WriteFile(list_path, data.data(), data.size())); identifiers = Profile::LoadUserProfileList(list_path); EXPECT_EQ(2, identifiers.size()); EXPECT_EQ(kUser0, identifiers[0].user); EXPECT_EQ(kIdentifier0, identifiers[0].identifier); EXPECT_EQ(kHash0, identifiers[0].user_hash); EXPECT_EQ(kUser1, identifiers[1].user); EXPECT_EQ(kIdentifier1, identifiers[1].identifier); EXPECT_EQ("", identifiers[1].user_hash); } TEST_F(ProfileTest, SaveUserProfileList) { const char kUser0[] = "user0"; const char kIdentifier0[] = "id0"; Profile::Identifier id0(kUser0, kIdentifier0); const char kHash0[] = "hash0"; id0.user_hash = kHash0; vector profiles; profiles.push_back(new Profile( control_interface(), metrics(), manager(), id0, FilePath(), false)); const char kUser1[] = "user1"; const char kIdentifier1[] = "id1"; Profile::Identifier id1(kUser1, kIdentifier1); const char kHash1[] = "hash1"; id1.user_hash = kHash1; profiles.push_back(new Profile( control_interface(), metrics(), manager(), id1, FilePath(), false)); const char kIdentifier2[] = "id2"; Profile::Identifier id2("", kIdentifier2); const char kHash2[] = "hash2"; id1.user_hash = kHash2; profiles.push_back(new Profile( control_interface(), metrics(), manager(), id2, FilePath(), false)); FilePath list_path(FilePath(storage_path()).Append("test.profile")); EXPECT_TRUE(Profile::SaveUserProfileList(list_path, profiles)); string profile_data; EXPECT_TRUE(base::ReadFileToString(list_path, &profile_data)); EXPECT_EQ(base::StringPrintf("~%s/%s %s\n~%s/%s %s\n", kUser0, kIdentifier0, kHash0, kUser1, kIdentifier1, kHash1), profile_data); } TEST_F(ProfileTest, MatchesIdentifier) { static const char kUser[] = "theUser"; static const char kIdentifier[] = "theIdentifier"; Profile::Identifier id(kUser, kIdentifier); ProfileRefPtr profile(new Profile( control_interface(), metrics(), manager(), id, FilePath(), false)); EXPECT_TRUE(profile->MatchesIdentifier(id)); EXPECT_FALSE(profile->MatchesIdentifier(Profile::Identifier(kUser, ""))); EXPECT_FALSE( profile->MatchesIdentifier(Profile::Identifier("", kIdentifier))); EXPECT_FALSE( profile->MatchesIdentifier(Profile::Identifier(kIdentifier, kUser))); } TEST_F(ProfileTest, InitStorage) { Profile::Identifier id("theUser", "theIdentifier"); ASSERT_TRUE(base::CreateDirectory( base::FilePath(storage_path()).Append("theUser"))); // Profile doesn't exist but we wanted it to. EXPECT_FALSE(ProfileInitStorage(id, Profile::kOpenExisting, false, Error::kNotFound)); // Success case, with a side effect of creating the profile. EXPECT_TRUE(ProfileInitStorage(id, Profile::kCreateNew, true, Error::kSuccess)); // The results from our two test cases above will now invert since // the profile now exists. First, we now succeed if we require that // the profile already exist... EXPECT_TRUE(ProfileInitStorage(id, Profile::kOpenExisting, false, Error::kSuccess)); // And we fail if we require that it doesn't. EXPECT_FALSE(ProfileInitStorage(id, Profile::kCreateNew, false, Error::kAlreadyExists)); // As a sanity check, ensure "create or open" works for both profile-exists... EXPECT_TRUE(ProfileInitStorage(id, Profile::kCreateOrOpenExisting, false, Error::kSuccess)); // ...and for a new profile that doesn't exist. Profile::Identifier id2("theUser", "theIdentifier2"); // Let's just make double-check that this profile really doesn't exist. ASSERT_FALSE(ProfileInitStorage(id2, Profile::kOpenExisting, false, Error::kNotFound)); // Then test that with "create or open" we succeed. EXPECT_TRUE(ProfileInitStorage(id2, Profile::kCreateOrOpenExisting, false, Error::kSuccess)); // Corrupt the profile storage. FilePath final_path( base::StringPrintf("%s/%s/%s.profile", storage_path().c_str(), id.user.c_str(), id.identifier.c_str())); #ifdef ENABLE_JSON_STORE final_path = final_path.AddExtension("json"); #endif string data = "]corrupt_data["; EXPECT_EQ(data.size(), base::WriteFile(final_path, data.data(), data.size())); // Then test that we fail to open this file. EXPECT_CALL(*mock_metrics_, NotifyCorruptedProfile()); EXPECT_FALSE(ProfileInitStorage(id, Profile::kOpenExisting, false, Error::kInternalError)); Mock::VerifyAndClearExpectations(mock_metrics_.get()); // But then on a second try the file no longer exists. EXPECT_CALL(*mock_metrics_, NotifyCorruptedProfile()).Times(0); ASSERT_FALSE(ProfileInitStorage(id, Profile::kOpenExisting, false, Error::kNotFound)); } TEST_F(ProfileTest, UpdateDevice) { EXPECT_FALSE(profile_->UpdateDevice(nullptr)); } TEST_F(ProfileTest, GetServiceFromEntry) { std::unique_ptr manager(new StrictMock( control_interface(), dispatcher(), metrics())); profile_->manager_ = manager.get(); MockStore* storage(new StrictMock()); profile_->storage_.reset(storage); // Passes ownership const string kEntryName("entry_name"); // If entry does not appear in storage, GetServiceFromEntry() should return // an error. EXPECT_CALL(*storage, ContainsGroup(kEntryName)) .WillOnce(Return(false)); { Error error; profile_->GetServiceFromEntry(kEntryName, &error); EXPECT_EQ(Error::kNotFound, error.type()); } Mock::VerifyAndClearExpectations(storage); EXPECT_CALL(*storage, ContainsGroup(kEntryName)) .WillRepeatedly(Return(true)); // Service entry already registered with the manager, the registered service // is returned. scoped_refptr registered_service(CreateMockService()); EXPECT_CALL(*manager.get(), GetServiceWithStorageIdentifier(profile_, kEntryName, _)) .WillOnce(Return(registered_service)); { Error error; EXPECT_EQ(registered_service, profile_->GetServiceFromEntry(kEntryName, &error)); EXPECT_TRUE(error.IsSuccess()); } Mock::VerifyAndClearExpectations(manager.get()); // Service entry not registered with the manager, a temporary service is // created/returned. scoped_refptr temporary_service(CreateMockService()); EXPECT_CALL(*manager.get(), GetServiceWithStorageIdentifier(profile_, kEntryName, _)) .WillOnce(Return(nullptr)); EXPECT_CALL(*manager.get(), CreateTemporaryServiceFromProfile(profile_, kEntryName, _)) .WillOnce(Return(temporary_service)); { Error error; EXPECT_EQ(temporary_service, profile_->GetServiceFromEntry(kEntryName, &error)); EXPECT_TRUE(error.IsSuccess()); } Mock::VerifyAndClearExpectations(manager.get()); } } // namespace shill