blob: 4e4392893b802db6261e64134c3bfc8cfd2b194d [file] [log] [blame]
// Copyright (c) 2012 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/profile.h"
#include <string>
#include <vector>
#include <base/files/file_util.h>
#include <base/strings/stringprintf.h>
#include <base/strings/string_util.h>
#include <gtest/gtest.h>
#include "shill/glib.h"
#include "shill/key_file_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"
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, "", false);
}
MockService* CreateMockService() {
return new StrictMock<MockService>(control_interface(),
dispatcher(),
metrics(),
manager());
}
virtual void SetUp() {
PropertyStoreTest::SetUp();
FilePath final_path(storage_path());
final_path = final_path.Append("test.profile");
std::unique_ptr<KeyFileStore> storage(new KeyFileStore(&real_glib_));
storage->set_path(final_path);
ASSERT_TRUE(storage->Open());
profile_->set_storage(storage.release()); // Passes ownership.
}
bool ProfileInitStorage(const Profile::Identifier& id,
Profile::InitStorageOption storage_option,
bool save,
Error::Type error_type) {
Error error;
ProfileRefPtr profile(
new Profile(control_interface(), mock_metrics_.get(), manager(), id,
storage_path(), false));
bool ret = profile->InitStorage(&real_glib_, storage_option, &error);
EXPECT_EQ(error_type, error.type());
if (ret && save) {
EXPECT_TRUE(profile->Save());
}
return ret;
}
protected:
GLib real_glib_;
std::unique_ptr<MockMetrics> mock_metrics_;
ProfileRefPtr profile_;
};
TEST_F(ProfileTest, DeleteEntry) {
std::unique_ptr<MockManager> manager(new StrictMock<MockManager>(
control_interface(), dispatcher(), metrics(), glib()));
profile_->manager_ = manager.get();
MockStore* storage(new StrictMock<MockStore>());
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, "", false));
EXPECT_EQ(kIdentifier, profile->GetFriendlyName());
id.user = kUser;
profile =
new Profile(control_interface(), metrics(), manager(), id, "", 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/";
FilePath path;
Profile::Identifier id(kIdentifier);
ProfileRefPtr profile(
new Profile(control_interface(), metrics(), manager(), id, "", false));
EXPECT_FALSE(profile->GetStoragePath(&path));
id.user = kUser;
profile =
new Profile(control_interface(), metrics(), manager(), id, kDirectory,
false);
EXPECT_TRUE(profile->GetStoragePath(&path));
EXPECT_EQ("/a/place/for/chronos/someprofile.profile", path.value());
}
TEST_F(ProfileTest, ServiceManagement) {
scoped_refptr<MockService> service1(CreateMockService());
scoped_refptr<MockService> 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 prioirty 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<MockService> service1(CreateMockService());
scoped_refptr<MockService> 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<MockService> service1(CreateMockService());
scoped_refptr<MockService> 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<Profile::Identifier> 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<ProfileRefPtr> profiles;
profiles.push_back(new Profile(control_interface(), metrics(), manager(),
id0, "", 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, "", 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, "", 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, "", 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()));
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<MockManager> manager(new StrictMock<MockManager>(
control_interface(), dispatcher(), metrics(), glib()));
profile_->manager_ = manager.get();
MockStore* storage(new StrictMock<MockStore>());
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<MockService> 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<MockService> 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