// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/chrome/browser/sessions/model/session_restoration_service_factory.h"
#import "base/run_loop.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/task_environment.h"
#import "base/types/cxx23_to_underlying.h"
#import "components/prefs/pref_service.h"
#import "ios/chrome/browser/sessions/model/proto/storage.pb.h"
#import "ios/chrome/browser/sessions/model/session_constants.h"
#import "ios/chrome/browser/sessions/model/session_internal_util.h"
#import "ios/chrome/browser/sessions/model/session_window_ios.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
namespace {
// Configures preferences storing session storage format and session storage
// migration status.
void WriteSessionStoragePref(PrefService* prefs,
SessionStorageFormat storage_format,
SessionStorageMigrationStatus migration_status,
base::Time last_migration_attempt_time) {
prefs->SetInteger(kSessionStorageFormatPref,
base::to_underlying(storage_format));
prefs->SetInteger(kSessionStorageMigrationStatusPref,
base::to_underlying(migration_status));
prefs->SetTime(kSessionStorageMigrationStartedTimePref,
last_migration_attempt_time);
}
// Checks preferences storing session storage format and session storage
// migration status have expected values.
void CheckSessionStoragePref(PrefService* prefs,
SessionStorageFormat storage_format,
SessionStorageMigrationStatus migration_status) {
EXPECT_EQ(prefs->GetInteger(kSessionStorageFormatPref),
base::to_underlying(storage_format));
EXPECT_EQ(prefs->GetInteger(kSessionStorageMigrationStatusPref),
base::to_underlying(migration_status));
}
// Name of the session used by the tests (random string obtained by
// running `uuidgen` on the command-line, no meaning to it).
const char kSessionIdentifier[] = "2D357BD5-2867-482F-A164-E7EF8EEED6AF";
// Returns the path of the legacy session named `identifier` in `root`.
base::FilePath LegacySessionPath(const base::FilePath& root,
const std::string& identifier) {
return root.Append(kLegacySessionsDirname)
.Append(identifier)
.Append(kLegacySessionFilename);
}
// Creates an empty session in legacy format named `identifier` in `root`.
bool CreateLegacySession(const base::FilePath& root,
const std::string& identifier) {
SessionWindowIOS* session =
[[SessionWindowIOS alloc] initWithSessions:@[]
tabGroups:@[]
selectedIndex:NSNotFound];
const base::FilePath session_path = LegacySessionPath(root, identifier);
return ios::sessions::WriteSessionWindow(session_path, session);
}
// Returns whether a legacy session named `identifier` exists in `root`.
bool LegacySessionExists(const base::FilePath& root,
const std::string& identifier) {
const base::FilePath session_path = LegacySessionPath(root, identifier);
return ios::sessions::ReadSessionWindow(session_path) != nil;
}
// Returns the path of the optimized session named `identifier` in `root`.
base::FilePath OptimizedSessionPath(const base::FilePath& root,
const std::string& identifier) {
return root.Append(kSessionRestorationDirname)
.Append(identifier)
.Append(kSessionMetadataFilename);
}
// Creates an empty session in optimized format named `identifier` in `root`.
bool CreateOptimizedSession(const base::FilePath& root,
const std::string& identifier) {
ios::proto::WebStateListStorage session_storage;
session_storage.set_active_index(-1);
const base::FilePath session_path = OptimizedSessionPath(root, identifier);
return ios::sessions::WriteProto(session_path, session_storage);
}
// Returns whether a legacy session named `identifier` exists in `root`.
bool OptimizedSessionExists(const base::FilePath& root,
const std::string& identifier) {
ios::proto::WebStateListStorage session_storage;
const base::FilePath session_path = OptimizedSessionPath(root, identifier);
return ios::sessions::ParseProto(session_path, session_storage);
}
} // namespace
class SessionRestorationServiceFactoryTest : public PlatformTest {
public:
SessionRestorationServiceFactoryTest()
: browser_state_(TestChromeBrowserState::Builder().Build()) {}
ChromeBrowserState* browser_state() { return browser_state_.get(); }
ChromeBrowserState* otr_browser_state() {
return browser_state_->GetOffTheRecordChromeBrowserState();
}
const base::HistogramTester& histogram_tester() const {
return histogram_tester_;
}
private:
base::test::TaskEnvironment task_environment_;
std::unique_ptr<TestChromeBrowserState> browser_state_;
base::HistogramTester histogram_tester_;
};
// Tests that the factory correctly instantiate a new service when the storage
// format is "unknown".
TEST_F(SessionRestorationServiceFactoryTest, CreateInstance_Unknown) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_TRUE(
SessionRestorationServiceFactory::GetForBrowserState(browser_state()));
}
// Tests that the factory correctly instantiate a new service for off-the-record
// BrowserState when the storage format is "unknown".
TEST_F(SessionRestorationServiceFactoryTest, CreateOTRInstance_Unknown) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_TRUE(SessionRestorationServiceFactory::GetForBrowserState(
otr_browser_state()));
}
// Tests that regular and off-the-record BrowserState uses distinct instances
// when the storage format is "unknown".
TEST_F(SessionRestorationServiceFactoryTest, InstancesAreDistinct_Unknown) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_NE(
SessionRestorationServiceFactory::GetForBrowserState(browser_state()),
SessionRestorationServiceFactory::GetForBrowserState(
otr_browser_state()));
}
// Tests that the factory correctly instantiate a new service when using
// the "legacy" storage.
TEST_F(SessionRestorationServiceFactoryTest, CreateInstance_Legacy) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_TRUE(
SessionRestorationServiceFactory::GetForBrowserState(browser_state()));
}
// Tests that the factory correctly instantiate a new service for off-the-record
// BrowserState when using the "legacy" storage.
TEST_F(SessionRestorationServiceFactoryTest, CreateOTRInstance_Legacy) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_TRUE(SessionRestorationServiceFactory::GetForBrowserState(
otr_browser_state()));
}
// Tests that regular and off-the-record BrowserState uses distinct instances
// when using the "legacy" storage.
TEST_F(SessionRestorationServiceFactoryTest, InstancesAreDistinct_Legacy) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_NE(
SessionRestorationServiceFactory::GetForBrowserState(browser_state()),
SessionRestorationServiceFactory::GetForBrowserState(
otr_browser_state()));
}
// Tests that the factory correctly instantiate a new service when using
// the "optimized" storage.
TEST_F(SessionRestorationServiceFactoryTest, CreateInstance_Optimized) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_TRUE(
SessionRestorationServiceFactory::GetForBrowserState(browser_state()));
}
// Tests that the factory correctly instantiate a new service for off-the-record
// BrowserState when using the "optimized" storage.
TEST_F(SessionRestorationServiceFactoryTest, CreateOTRInstance_Optimized) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_TRUE(SessionRestorationServiceFactory::GetForBrowserState(
otr_browser_state()));
}
// Tests that regular and off-the-record BrowserState uses distinct instances
// when using the "optimized" storage.
TEST_F(SessionRestorationServiceFactoryTest, InstancesAreDistinct_Optimized) {
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess, base::Time());
EXPECT_NE(
SessionRestorationServiceFactory::GetForBrowserState(browser_state()),
SessionRestorationServiceFactory::GetForBrowserState(
otr_browser_state()));
}
// Tests that MigrateSessionStorage(...) succeed when asked to migrate to
// legacy storage when the storage format known to be in legacy and check
// the operation is synchronous.
TEST_F(SessionRestorationServiceFactoryTest, MigrateSession_ToLegacy_Legacy) {
// Create an empty session in legacy format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateLegacySession(root, kSessionIdentifier));
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that is is immediate and does not require
// to sping the main run loop.
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure));
EXPECT_TRUE(callback_called);
// Check that the session storage is in the legacy format.
EXPECT_TRUE(LegacySessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does not perform conversion when the
// storage format is unknown, and instead detect the existing format. When no
// previous session exists, and thus the detection must succeed, and reports
// the storage is in the requested format.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_UnknownInexistent) {
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre());
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does not perform conversion when the
// storage format is unknown, and instead detect the existing format. When a
// previous session in legacy format exists, it should leave it untouched,
// the detection should report success, and that the storage is in legacy
// format.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_UnknownAsLegacy) {
// Create an empty session in legacy format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateLegacySession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kUnkown, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the legacy format.
EXPECT_TRUE(LegacySessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre());
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does not perform conversion when the
// storage format is unknown, and instead detect the existing format. When a
// previous session in optimized format exists, it should leave it untouched,
// the detection should report success, and that the storage is in optimized
// format.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_UnknownAsOptimized) {
// Create an empty session in optimized format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateOptimizedSession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kUnkown, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(OptimizedSessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre());
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) succeed when asked to migrate to
// legacy storage when the storage format is in optimized format, and thus
// requires conversion.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_Optimized) {
// Create an empty session in optimized format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateOptimizedSession(root, kSessionIdentifier));
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the legacy format.
EXPECT_TRUE(LegacySessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) will mark the migration as failed
// if it cannot convert the storage from optimized to legacy.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_OptimizedFailureMigration) {
// Write a broken session in optimized format.
const base::FilePath root = browser_state()->GetStatePath();
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
const base::FilePath path = OptimizedSessionPath(root, kSessionIdentifier);
ASSERT_TRUE(ios::sessions::WriteFile(path, data));
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the legacy session has not been created, and that the old
// corrupt session is still present.
EXPECT_FALSE(LegacySessionExists(root, kSessionIdentifier));
EXPECT_NSEQ(ios::sessions::ReadFile(path), data);
// Check that the preferences have been updated, and the migration marked
// as failed.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kFailure);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kFailure, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) does nothing synchronously if
// asked to migrate session to legacy but the previous migration failed.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_OptimizedPreviousMigrationFailed) {
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kFailure,
base::Time::Now() - base::Hours(1));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that is is immediate and does not require
// to sping the main run loop.
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure));
EXPECT_TRUE(callback_called);
// Check that the preferences have been not updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kFailure);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kFailure, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does nothing synchronously if
// asked to migrate session to legacy but the application crashed while
// the previous migration was in progress.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_OptimizedPreviousMigrationCrashedInProgress) {
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kInProgress,
base::Time::Now() - base::Hours(1));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that is is immediate and does not require
// to sping the main run loop.
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure));
EXPECT_TRUE(callback_called);
// Check that the preferences have been not updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kInProgress);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(base::Bucket(
SessionHistogramStorageMigrationStatus::kInterrupted, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) retry the migration from optimized
// to migration if the previous attempt happened a while ago but failed.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_OptimizedRetryMigrationFailed) {
// Create an empty session in optimized format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateOptimizedSession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kFailure,
base::Time::Now() - base::Days(7));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the legacy format.
EXPECT_TRUE(LegacySessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) retry the migration from optimized
// to migration if the previous attempt happened a while ago but was
// interrupted.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToLegacy_OptimizedRetryMigrationCrashedInProgress) {
// Create an empty session in optimized format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateOptimizedSession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kInProgress,
base::Time::Now() - base::Days(7));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kLegacy,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the legacy format.
EXPECT_TRUE(LegacySessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) succeed when asked to migrate to
// optimized storage when the storage format known to be in optimized and check
// the operation is synchronous.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_Optimized) {
// Create an empty session in optimized format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateOptimizedSession(root, kSessionIdentifier));
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that is is immediate and does not require
// to sping the main run loop.
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure));
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(OptimizedSessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does not perform conversion when the
// storage format is unknown, and instead detect the existing format. When no
// previous session exists, and thus the detection must succeed, and reports
// the storage is in the requested format.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_UnknownInexistent) {
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre());
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does not perform conversion when the
// storage format is unknown, and instead detect the existing format. When a
// previous session in legacy format exists, it should leave it untouched,
// the detection should report success, and that the storage is in legacy
// format.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_UnknownAsOptimized) {
// Create an empty session in optimized format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateOptimizedSession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kUnkown, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(OptimizedSessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre());
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does not perform conversion when the
// storage format is unknown, and instead detect the existing format. When a
// previous session in optimized format exists, it should leave it untouched,
// the detection should report success, and that the storage is in optimized
// format.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_UnknownAsLegacy) {
// Create an empty session in legacy format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateLegacySession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kUnknown,
SessionStorageMigrationStatus::kUnkown, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(LegacySessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre());
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) succeed when asked to migrate to
// optimized storage when the storage format is in legacy format, and thus
// requires conversion.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_Legacy) {
// Create an empty session in legacy format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateLegacySession(root, kSessionIdentifier));
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(OptimizedSessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) will mark the migration as failed
// if it cannot convert the storage from legacy to optimized.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_LegacyFailureMigration) {
// Write a broken session in legacy format.
const base::FilePath root = browser_state()->GetStatePath();
NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
const base::FilePath path = LegacySessionPath(root, kSessionIdentifier);
ASSERT_TRUE(ios::sessions::WriteFile(path, data));
WriteSessionStoragePref(
browser_state()->GetPrefs(), SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kSuccess, base::Time());
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the optimized session has not been created, and that the old
// corrupt session is still present.
EXPECT_FALSE(OptimizedSessionExists(root, kSessionIdentifier));
EXPECT_NSEQ(ios::sessions::ReadFile(path), data);
// Check that the preferences have been updated, and the migration marked
// as failed.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kFailure);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kFailure, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) does nothing synchronously if
// asked to migrate session to optimized but the previous migration failed.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_LegacyPreviousMigrationFailed) {
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kFailure,
base::Time::Now() - base::Hours(1));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that is is immediate and does not require
// to sping the main run loop.
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure));
EXPECT_TRUE(callback_called);
// Check that the preferences have been not updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kFailure);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kFailure, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) does nothing synchronously if
// asked to migrate session to optimized but the application crashed while
// the previous migration was in progress.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_LegacyPreviousMigrationCrashedInProgress) {
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kInProgress,
base::Time::Now() - base::Hours(1));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that is is immediate and does not require
// to sping the main run loop.
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure));
EXPECT_TRUE(callback_called);
// Check that the preferences have been not updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kInProgress);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kLegacy, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(base::Bucket(
SessionHistogramStorageMigrationStatus::kInterrupted, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
base::BucketsAre());
}
// Tests that MigrateSessionStorage(...) retry the migration from optimized
// to migration if the previous attempt happened a while ago but failed.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_LegacyRetryMigrationFailed) {
// Create an empty session in legacy format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateLegacySession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kFailure,
base::Time::Now() - base::Days(7));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(OptimizedSessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}
// Tests that MigrateSessionStorage(...) succeed when asked to migrate to
// optimized storage when the storage format is in legacy format, and thus
// requires conversion.
TEST_F(SessionRestorationServiceFactoryTest,
MigrateSession_ToOptimized_LegacyRetryMigrationCrashedInProgress) {
// Create an empty session in legacy format.
const base::FilePath& root = browser_state()->GetStatePath();
ASSERT_TRUE(CreateLegacySession(root, kSessionIdentifier));
WriteSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kLegacy,
SessionStorageMigrationStatus::kInProgress,
base::Time::Now() - base::Days(7));
bool callback_called = false;
base::OnceClosure closure =
base::BindOnce([](bool* invoked) { *invoked = true; }, &callback_called);
// Start the migration, and check that it is not immediate, but requires
// to spin the main run loop.
base::RunLoop run_loop;
SessionRestorationServiceFactory::GetInstance()->MigrateSessionStorageFormat(
browser_state(), SessionRestorationServiceFactory::kOptimized,
std::move(closure).Then(run_loop.QuitClosure()));
EXPECT_FALSE(callback_called);
// The callback should eventually be called, but asynchronously.
run_loop.Run();
EXPECT_TRUE(callback_called);
// Check that the session storage is in the optimized format.
EXPECT_TRUE(OptimizedSessionExists(root, kSessionIdentifier));
// Check that the preferences have been updated.
CheckSessionStoragePref(browser_state()->GetPrefs(),
SessionStorageFormat::kOptimized,
SessionStorageMigrationStatus::kSuccess);
// Check that the expected metrics have been recorded.
EXPECT_THAT(histogram_tester().GetAllSamples(kSessionHistogramStorageFormat),
base::BucketsAre(
base::Bucket(SessionHistogramStorageFormat::kOptimized, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationStatus),
base::BucketsAre(
base::Bucket(SessionHistogramStorageMigrationStatus::kSuccess, 1)));
EXPECT_THAT(
histogram_tester().GetAllSamples(kSessionHistogramStorageMigrationTiming),
testing::Not(base::BucketsAre()));
}