blob: 2afdf5e4296a7bcb64e3c1586df0721708923f77 [file] [log] [blame]
// Copyright (c) 2010 The Chromium 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 "base/file_path.h"
#include "base/file_util.h"
#include "base/scoped_ptr.h"
#include "base/scoped_temp_dir.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "net/base/test_completion_callback.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webkit/database/database_tracker.h"
namespace {
class TestObserver : public webkit_database::DatabaseTracker::Observer {
public:
TestObserver() : new_notification_received_(false) {}
virtual ~TestObserver() {}
virtual void OnDatabaseSizeChanged(const string16& origin_identifier,
const string16& database_name,
int64 database_size,
int64 space_available) {
new_notification_received_ = true;
origin_identifier_ = origin_identifier;
database_name_ = database_name;
database_size_ = database_size;
space_available_ = space_available;
}
virtual void OnDatabaseScheduledForDeletion(const string16& origin_identifier,
const string16& database_name) {
new_notification_received_ = true;
origin_identifier_ = origin_identifier;
database_name_ = database_name;
}
bool DidReceiveNewNotification() {
bool temp_new_notification_received = new_notification_received_;
new_notification_received_ = false;
return temp_new_notification_received;
}
string16 GetNotificationOriginIdentifier() { return origin_identifier_; }
string16 GetNotificationDatabaseName() { return database_name_; }
int64 GetNotificationDatabaseSize() { return database_size_; }
int64 GetNotificationSpaceAvailable() { return space_available_; }
private:
bool new_notification_received_;
string16 origin_identifier_;
string16 database_name_;
int64 database_size_;
int64 space_available_;
};
void CheckNotificationReceived(TestObserver* observer,
const string16& expected_origin_identifier,
const string16& expected_database_name,
int64 expected_database_size,
int64 expected_space_available) {
EXPECT_TRUE(observer->DidReceiveNewNotification());
EXPECT_EQ(expected_origin_identifier,
observer->GetNotificationOriginIdentifier());
EXPECT_EQ(expected_database_name,
observer->GetNotificationDatabaseName());
EXPECT_EQ(expected_database_size,
observer->GetNotificationDatabaseSize());
EXPECT_EQ(expected_space_available,
observer->GetNotificationSpaceAvailable());
}
} // namespace
namespace webkit_database {
// We declare a helper class, and make it a friend of DatabaseTracker using
// the FRIEND_TEST() macro, and we implement all tests we want to run as
// static methods of this class. Then we make our TEST() targets call these
// static functions. This allows us to run each test in normal mode and
// incognito mode without writing the same code twice.
class DatabaseTracker_TestHelper_Test {
public:
static void TestDeleteOpenDatabase(bool incognito_mode) {
// Initialize the tracker database.
ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
scoped_refptr<DatabaseTracker> tracker(
new DatabaseTracker(temp_dir.path(), incognito_mode));
// Create and open three databases.
int64 database_size = 0;
int64 space_available = 0;
const string16 kOrigin1 = ASCIIToUTF16("origin1");
const string16 kOrigin2 = ASCIIToUTF16("origin2");
const string16 kDB1 = ASCIIToUTF16("db1");
const string16 kDB2 = ASCIIToUTF16("db2");
const string16 kDB3 = ASCIIToUTF16("db3");
const string16 kDescription = ASCIIToUTF16("database_description");
tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
&database_size, &space_available);
tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0,
&database_size, &space_available);
tracker->DatabaseOpened(kOrigin2, kDB3, kDescription, 0,
&database_size, &space_available);
EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(
tracker->GetOriginDirectory(kOrigin1))))));
EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(
tracker->GetOriginDirectory(kOrigin2))))));
EXPECT_EQ(1, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
EXPECT_EQ(2, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin2, kDB2), "aa", 2));
EXPECT_EQ(3, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin2, kDB3), "aaa", 3));
tracker->DatabaseModified(kOrigin1, kDB1);
tracker->DatabaseModified(kOrigin2, kDB2);
tracker->DatabaseModified(kOrigin2, kDB3);
// Delete db1. Should also delete origin1.
TestObserver observer;
tracker->AddObserver(&observer);
TestCompletionCallback callback;
int result = tracker->DeleteDatabase(kOrigin1, kDB1, &callback);
EXPECT_EQ(net::ERR_IO_PENDING, result);
ASSERT_FALSE(callback.have_result());
EXPECT_TRUE(observer.DidReceiveNewNotification());
EXPECT_EQ(kOrigin1, observer.GetNotificationOriginIdentifier());
EXPECT_EQ(kDB1, observer.GetNotificationDatabaseName());
tracker->DatabaseClosed(kOrigin1, kDB1);
result = callback.GetResult(result);
EXPECT_EQ(net::OK, result);
EXPECT_FALSE(file_util::PathExists(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(kOrigin1)))));
// Recreate db1.
tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
&database_size, &space_available);
EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(
tracker->GetOriginDirectory(kOrigin1))))));
EXPECT_EQ(1, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
tracker->DatabaseModified(kOrigin1, kDB1);
// Setup file modification times. db1 and db2 are modified now, db3 three
// days ago.
EXPECT_TRUE(file_util::SetLastModifiedTime(
tracker->GetFullDBFilePath(kOrigin1, kDB1), base::Time::Now()));
EXPECT_TRUE(file_util::SetLastModifiedTime(
tracker->GetFullDBFilePath(kOrigin2, kDB2), base::Time::Now()));
base::Time three_days_ago = base::Time::Now();
three_days_ago -= base::TimeDelta::FromDays(3);
EXPECT_TRUE(file_util::SetLastModifiedTime(
tracker->GetFullDBFilePath(kOrigin2, kDB3), three_days_ago));
// Delete databases modified since yesterday. db2 is whitelisted.
base::Time yesterday = base::Time::Now();
yesterday -= base::TimeDelta::FromDays(1);
std::vector<string16> protected_origins;
protected_origins.push_back(kOrigin2);
result = tracker->DeleteDataModifiedSince(
yesterday, protected_origins, &callback);
EXPECT_EQ(net::ERR_IO_PENDING, result);
ASSERT_FALSE(callback.have_result());
EXPECT_TRUE(observer.DidReceiveNewNotification());
tracker->DatabaseClosed(kOrigin1, kDB1);
tracker->DatabaseClosed(kOrigin2, kDB2);
result = callback.GetResult(result);
EXPECT_EQ(net::OK, result);
EXPECT_FALSE(file_util::PathExists(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(kOrigin1)))));
EXPECT_TRUE(
file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB2)));
EXPECT_TRUE(
file_util::PathExists(tracker->GetFullDBFilePath(kOrigin2, kDB3)));
tracker->DatabaseClosed(kOrigin2, kDB3);
tracker->RemoveObserver(&observer);
}
static void TestDatabaseTracker(bool incognito_mode) {
// Initialize the tracker database.
ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
scoped_refptr<DatabaseTracker> tracker(
new DatabaseTracker(temp_dir.path(), incognito_mode));
// Add two observers.
TestObserver observer1;
TestObserver observer2;
tracker->AddObserver(&observer1);
tracker->AddObserver(&observer2);
// Open three new databases.
int64 database_size = 0;
int64 space_available = 0;
const string16 kOrigin1 = ASCIIToUTF16("origin1");
const string16 kOrigin2 = ASCIIToUTF16("origin2");
const string16 kDB1 = ASCIIToUTF16("db1");
const string16 kDB2 = ASCIIToUTF16("db2");
const string16 kDB3 = ASCIIToUTF16("db3");
const string16 kDescription = ASCIIToUTF16("database_description");
// Get the quota for kOrigin1 and kOrigin2
DatabaseTracker::CachedOriginInfo* origin1_info =
tracker->GetCachedOriginInfo(kOrigin1);
DatabaseTracker::CachedOriginInfo* origin2_info =
tracker->GetCachedOriginInfo(kOrigin1);
EXPECT_TRUE(origin1_info);
EXPECT_TRUE(origin2_info);
int64 origin1_quota = origin1_info->Quota();
int64 origin2_quota = origin2_info->Quota();
EXPECT_EQ(origin1_quota, tracker->GetOriginSpaceAvailable(kOrigin1));
EXPECT_EQ(origin2_quota, tracker->GetOriginSpaceAvailable(kOrigin2));
// Set a new quota for kOrigin1
origin1_quota *= 2;
tracker->SetOriginQuota(kOrigin1, origin1_quota);
origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
EXPECT_TRUE(origin1_info);
EXPECT_EQ(origin1_quota, origin1_info->Quota());
tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
&database_size, &space_available);
EXPECT_EQ(0, database_size);
EXPECT_EQ(origin1_quota, space_available);
tracker->DatabaseOpened(kOrigin2, kDB2, kDescription, 0,
&database_size, &space_available);
EXPECT_EQ(0, database_size);
EXPECT_EQ(origin2_quota, space_available);
tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0,
&database_size, &space_available);
EXPECT_EQ(0, database_size);
EXPECT_EQ(origin1_quota, space_available);
// Tell the tracker that a database has changed.
// Even though nothing has changed, the observers should be notified.
tracker->DatabaseModified(kOrigin1, kDB1);
CheckNotificationReceived(&observer1, kOrigin1, kDB1, 0, origin1_quota);
CheckNotificationReceived(&observer2, kOrigin1, kDB1, 0, origin1_quota);
// Write some data to each file and check that the listeners are
// called with the appropriate values.
EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(
tracker->GetOriginDirectory(kOrigin1))))));
EXPECT_TRUE(file_util::CreateDirectory(tracker->DatabaseDirectory().Append(
FilePath::FromWStringHack(UTF16ToWide(
tracker->GetOriginDirectory(kOrigin2))))));
EXPECT_EQ(1, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB1), "a", 1));
EXPECT_EQ(2, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin2, kDB2), "aa", 2));
EXPECT_EQ(4, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB3), "aaaa", 4));
tracker->DatabaseModified(kOrigin1, kDB1);
CheckNotificationReceived(&observer1, kOrigin1, kDB1, 1, origin1_quota - 1);
CheckNotificationReceived(&observer2, kOrigin1, kDB1, 1, origin1_quota - 1);
tracker->DatabaseModified(kOrigin2, kDB2);
CheckNotificationReceived(&observer1, kOrigin2, kDB2, 2, origin2_quota - 2);
CheckNotificationReceived(&observer2, kOrigin2, kDB2, 2, origin2_quota - 2);
tracker->DatabaseModified(kOrigin1, kDB3);
CheckNotificationReceived(&observer1, kOrigin1, kDB3, 4, origin1_quota - 5);
CheckNotificationReceived(&observer2, kOrigin1, kDB3, 4, origin1_quota - 5);
// Make sure the available space for kOrigin1 and kOrigin2 changed too.
EXPECT_EQ(origin1_quota - 5, tracker->GetOriginSpaceAvailable(kOrigin1));
EXPECT_EQ(origin2_quota - 2, tracker->GetOriginSpaceAvailable(kOrigin2));
// Close all databases
tracker->DatabaseClosed(kOrigin1, kDB1);
tracker->DatabaseClosed(kOrigin2, kDB2);
tracker->DatabaseClosed(kOrigin1, kDB3);
// Open an existing database and check the reported size
tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
&database_size, &space_available);
EXPECT_EQ(1, database_size);
EXPECT_EQ(origin1_quota - 5, space_available);
// Make sure that the observers are notified even if
// the size of the database hasn't changed.
EXPECT_EQ(1, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB1), "b", 1));
tracker->DatabaseModified(kOrigin1, kDB1);
CheckNotificationReceived(&observer1, kOrigin1, kDB1, 1, origin1_quota - 5);
CheckNotificationReceived(&observer2, kOrigin1, kDB1, 1, origin1_quota - 5);
tracker->DatabaseClosed(kOrigin1, kDB1);
// Remove an observer; this should clear all caches.
tracker->RemoveObserver(&observer2);
// Change kDB1's and kDB3's size and call tracker->DatabaseModified()
// for kDB1 only. If the caches were indeed cleared, then calling
// tracker->DatabaseModified() should re-populate the cache for
// kOrigin1 == kOrigin1, and thus, should pick up kDB3's size change too.
EXPECT_EQ(5, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB1), "ccccc", 5));
EXPECT_EQ(6, file_util::WriteFile(
tracker->GetFullDBFilePath(kOrigin1, kDB3), "dddddd", 6));
tracker->DatabaseModified(kOrigin1, kDB1);
CheckNotificationReceived(&observer1, kOrigin1, kDB1, 5,
origin1_quota - 11);
EXPECT_FALSE(observer2.DidReceiveNewNotification());
EXPECT_EQ(origin1_quota - 11, tracker->GetOriginSpaceAvailable(kOrigin1));
// Close the tracker database and clear all caches.
// Then make sure that DatabaseOpened() still returns the correct result.
tracker->CloseTrackerDatabaseAndClearCaches();
tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
&database_size, &space_available);
EXPECT_EQ(5, database_size);
EXPECT_EQ(origin1_quota - 11, space_available);
// Close the tracker database and clear all caches. Then make sure that
// DatabaseModified() still calls the observers with correct values.
tracker->CloseTrackerDatabaseAndClearCaches();
tracker->DatabaseModified(kOrigin1, kDB3);
CheckNotificationReceived(&observer1, kOrigin1, kDB3, 6,
origin1_quota - 11);
tracker->DatabaseClosed(kOrigin1, kDB1);
// Remove all observers.
tracker->RemoveObserver(&observer1);
// Trying to delete a database in use should fail
tracker->DatabaseOpened(kOrigin1, kDB3, kDescription, 0,
&database_size, &space_available);
EXPECT_FALSE(tracker->DeleteClosedDatabase(kOrigin1, kDB3));
origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
EXPECT_TRUE(origin1_info);
EXPECT_EQ(6, origin1_info->GetDatabaseSize(kDB3));
tracker->DatabaseClosed(kOrigin1, kDB3);
// Delete a database and make sure the space used by that origin is updated
EXPECT_TRUE(tracker->DeleteClosedDatabase(kOrigin1, kDB3));
origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
EXPECT_TRUE(origin1_info);
EXPECT_EQ(origin1_quota - 5, tracker->GetOriginSpaceAvailable(kOrigin1));
EXPECT_EQ(5, origin1_info->GetDatabaseSize(kDB1));
EXPECT_EQ(0, origin1_info->GetDatabaseSize(kDB3));
// Get all data for all origins
std::vector<OriginInfo> origins_info;
EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info));
EXPECT_EQ(size_t(2), origins_info.size());
EXPECT_EQ(kOrigin1, origins_info[0].GetOrigin());
EXPECT_EQ(5, origins_info[0].TotalSize());
EXPECT_EQ(origin1_quota, origins_info[0].Quota());
EXPECT_EQ(5, origins_info[0].GetDatabaseSize(kDB1));
EXPECT_EQ(0, origins_info[0].GetDatabaseSize(kDB3));
EXPECT_EQ(kOrigin2, origins_info[1].GetOrigin());
EXPECT_EQ(2, origins_info[1].TotalSize());
EXPECT_EQ(origin2_quota, origins_info[1].Quota());
// Trying to delete an origin with databases in use should fail
tracker->DatabaseOpened(kOrigin1, kDB1, kDescription, 0,
&database_size, &space_available);
EXPECT_FALSE(tracker->DeleteOrigin(kOrigin1));
origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
EXPECT_TRUE(origin1_info);
EXPECT_EQ(5, origin1_info->GetDatabaseSize(kDB1));
tracker->DatabaseClosed(kOrigin1, kDB1);
// Delete an origin that doesn't have any database in use
EXPECT_TRUE(tracker->DeleteOrigin(kOrigin1));
origins_info.clear();
EXPECT_TRUE(tracker->GetAllOriginsInfo(&origins_info));
EXPECT_EQ(size_t(1), origins_info.size());
EXPECT_EQ(kOrigin2, origins_info[0].GetOrigin());
origin1_info = tracker->GetCachedOriginInfo(kOrigin1);
EXPECT_TRUE(origin1_info);
EXPECT_EQ(origin1_quota, origin1_info->Quota());
EXPECT_EQ(0, origin1_info->TotalSize());
EXPECT_EQ(origin1_quota, tracker->GetOriginSpaceAvailable(kOrigin1));
}
};
TEST(DatabaseTrackerTest, DeleteOpenDatabase) {
DatabaseTracker_TestHelper_Test::TestDeleteOpenDatabase(false);
}
TEST(DatabaseTrackerTest, DeleteOpenDatabaseIncognitoMode) {
DatabaseTracker_TestHelper_Test::TestDeleteOpenDatabase(true);
}
TEST(DatabaseTrackerTest, DatabaseTracker) {
DatabaseTracker_TestHelper_Test::TestDatabaseTracker(false);
}
TEST(DatabaseTrackerTest, DatabaseTrackerIncognitoMode) {
DatabaseTracker_TestHelper_Test::TestDatabaseTracker(true);
}
} // namespace webkit_database