mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
Add support for temporary storage via a new config option `temporary_storage` and a corresponding command-line argument `--temporary-storage`. When enabled, client storage uses a storage directory (e.g. `clientstorage/temporary/<uuid>`) instead of the shared default location. This can be used to provide isolation between concurrent servo instances. This is especially useful for WPT runs, where multiple Servo instances may execute in parallel and would otherwise share the same storage, leading to cross-test interference. Based on that, this PR also updates the WPT runner to enable temporary storage by default. Testing: Manual testing and a full try run. Signed-off-by: Jan Varga <jvarga@igalia.com>
436 lines
13 KiB
Rust
436 lines
13 KiB
Rust
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
use profile::mem as profile_mem;
|
|
use servo_base::generic_channel as base_channel;
|
|
use servo_base::generic_channel::GenericSend;
|
|
use servo_base::id::TEST_WEBVIEW_ID;
|
|
use servo_default_resources as _;
|
|
use servo_url::{ImmutableOrigin, ServoUrl};
|
|
use storage_traits::StorageThreads;
|
|
use storage_traits::webstorage_thread::{WebStorageThreadMsg, WebStorageType};
|
|
use tempfile::TempDir;
|
|
|
|
pub(crate) struct WebStorageTest {
|
|
tmp_dir: Option<TempDir>,
|
|
threads: StorageThreads,
|
|
}
|
|
|
|
impl WebStorageTest {
|
|
pub(crate) fn new() -> Self {
|
|
let tmp_dir = tempfile::tempdir().unwrap();
|
|
let config_dir = tmp_dir.path().to_path_buf();
|
|
let mem_profiler_chan = profile_mem::Profiler::create();
|
|
let threads = storage::new_storage_threads(mem_profiler_chan, Some(config_dir), false);
|
|
|
|
Self {
|
|
tmp_dir: Some(tmp_dir),
|
|
threads: threads.0,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn new_in_memory() -> Self {
|
|
let mem_profiler_chan = profile_mem::Profiler::create();
|
|
let threads = storage::new_storage_threads(mem_profiler_chan, None, false);
|
|
|
|
Self {
|
|
tmp_dir: None,
|
|
threads: threads.0,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn restart(mut self) -> Self {
|
|
let tmp_dir = self.tmp_dir.take();
|
|
let config_dir = tmp_dir.as_ref().map(|d| d.path().to_path_buf());
|
|
let mem_profiler_chan = profile_mem::Profiler::create();
|
|
let threads = storage::new_storage_threads(mem_profiler_chan, config_dir, false);
|
|
|
|
Self {
|
|
tmp_dir: tmp_dir,
|
|
threads: threads.0,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn threads(&self) -> StorageThreads {
|
|
self.threads.clone()
|
|
}
|
|
|
|
pub(crate) fn length(&self, storage_type: WebStorageType, origin: &ImmutableOrigin) -> usize {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::Length(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
pub(crate) fn key(
|
|
&self,
|
|
storage_type: WebStorageType,
|
|
origin: &ImmutableOrigin,
|
|
index: u32,
|
|
) -> Option<String> {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::Key(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
index,
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
pub(crate) fn keys(
|
|
&self,
|
|
storage_type: WebStorageType,
|
|
origin: &ImmutableOrigin,
|
|
) -> Vec<String> {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::Keys(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
pub(crate) fn get_item(
|
|
&self,
|
|
storage_type: WebStorageType,
|
|
origin: &ImmutableOrigin,
|
|
key: &str,
|
|
) -> Option<String> {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::GetItem(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
key.into(),
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
pub(crate) fn set_item(
|
|
&self,
|
|
storage_type: WebStorageType,
|
|
origin: &ImmutableOrigin,
|
|
key: &str,
|
|
value: &str,
|
|
) -> Result<(bool, Option<String>), ()> {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::SetItem(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
key.into(),
|
|
value.into(),
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
pub(crate) fn remove_item(
|
|
&self,
|
|
storage_type: WebStorageType,
|
|
origin: &ImmutableOrigin,
|
|
key: &str,
|
|
) -> Option<String> {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::RemoveItem(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
key.into(),
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
pub(crate) fn clear(&self, storage_type: WebStorageType, origin: &ImmutableOrigin) -> bool {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::Clear(
|
|
sender,
|
|
storage_type,
|
|
TEST_WEBVIEW_ID,
|
|
origin.clone(),
|
|
))
|
|
.unwrap();
|
|
receiver.recv().unwrap()
|
|
}
|
|
|
|
/// Gracefully shut down the webstorage thread to avoid dangling threads in tests.
|
|
fn shutdown(&self) {
|
|
let (sender, receiver) = base_channel::channel().unwrap();
|
|
self.threads
|
|
.send(WebStorageThreadMsg::Exit(sender))
|
|
.expect("failed to send Exit");
|
|
// Wait for acknowledgement so the thread terminates before the test ends.
|
|
let _ = receiver.recv();
|
|
}
|
|
}
|
|
|
|
impl Drop for WebStorageTest {
|
|
fn drop(&mut self) {
|
|
self.shutdown();
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn set_and_get_item() {
|
|
let test = WebStorageTest::new();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
|
|
// Set a value.
|
|
let result = test.set_item(WebStorageType::Local, &url.origin(), "foo", "bar");
|
|
assert_eq!(result, Ok((true, None)));
|
|
|
|
// Retrieve the value.
|
|
let result = test.get_item(WebStorageType::Local, &url.origin(), "foo");
|
|
assert_eq!(result, Some("bar".into()));
|
|
}
|
|
|
|
#[test]
|
|
fn set_and_get_item_in_memory() {
|
|
let test = WebStorageTest::new_in_memory();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
|
|
// Set a value.
|
|
let result = test.set_item(WebStorageType::Local, &url.origin(), "foo", "bar");
|
|
assert_eq!(result, Ok((true, None)));
|
|
|
|
// Retrieve the value.
|
|
let result = test.get_item(WebStorageType::Local, &url.origin(), "foo");
|
|
assert_eq!(result, Some("bar".into()));
|
|
}
|
|
|
|
#[test]
|
|
fn length_key_and_keys() {
|
|
let test = WebStorageTest::new();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
|
|
// Insert two items.
|
|
for (k, v) in [("foo", "v1"), ("bar", "v2")] {
|
|
let _ = test.set_item(WebStorageType::Local, &url.origin(), k, v);
|
|
}
|
|
|
|
// Verify length.
|
|
let result = test.length(WebStorageType::Local, &url.origin());
|
|
assert_eq!(result, 2);
|
|
|
|
// Verify key(0) returns one of the inserted keys.
|
|
let result = test.key(WebStorageType::Local, &url.origin(), 0);
|
|
let key0 = result.unwrap();
|
|
assert!(key0 == "foo" || key0 == "bar");
|
|
|
|
// Verify keys vector contains both keys.
|
|
let result = test.keys(WebStorageType::Local, &url.origin());
|
|
assert_eq!(result.len(), 2);
|
|
assert!(result.contains(&"foo".to_string()));
|
|
assert!(result.contains(&"bar".to_string()));
|
|
}
|
|
|
|
#[test]
|
|
fn remove_item_and_clear() {
|
|
let test = WebStorageTest::new();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
|
|
// Insert items.
|
|
for (k, v) in [("foo", "v1"), ("bar", "v2")] {
|
|
let _ = test.set_item(WebStorageType::Local, &url.origin(), k, v);
|
|
}
|
|
|
|
// Remove one item and verify old value is returned.
|
|
let result = test.remove_item(WebStorageType::Local, &url.origin(), "foo");
|
|
assert_eq!(result, Some("v1".into()));
|
|
|
|
// Removing again should return None.
|
|
let result = test.remove_item(WebStorageType::Local, &url.origin(), "foo");
|
|
assert_eq!(result, None);
|
|
|
|
// Clear storage and verify it reported change.
|
|
let result = test.clear(WebStorageType::Local, &url.origin());
|
|
assert!(result);
|
|
|
|
// Length should now be zero.
|
|
let result = test.length(WebStorageType::Local, &url.origin());
|
|
assert_eq!(result, 0);
|
|
}
|
|
|
|
fn test_origin_descriptors(
|
|
test: WebStorageTest,
|
|
storage_type: WebStorageType,
|
|
survives_restart: bool,
|
|
) {
|
|
let threads = test.threads();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
|
|
// Set a value.
|
|
let _ = test.set_item(storage_type, &url.origin(), "foo", "bar");
|
|
|
|
// Verify descriptors.
|
|
let descriptors = threads.webstorage_origins(storage_type);
|
|
assert_eq!(descriptors.len(), 1);
|
|
assert_eq!(descriptors[0].name, "https://example.com");
|
|
|
|
// Restart storage threads.
|
|
let test = test.restart();
|
|
let threads = test.threads();
|
|
|
|
// There should still be descriptors.
|
|
let descriptors = threads.webstorage_origins(storage_type);
|
|
if survives_restart {
|
|
assert_eq!(descriptors.len(), 1);
|
|
assert_eq!(descriptors[0].name, "https://example.com");
|
|
} else {
|
|
assert!(descriptors.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn origin_descriptors_session() {
|
|
let test = WebStorageTest::new();
|
|
test_origin_descriptors(
|
|
test,
|
|
WebStorageType::Session,
|
|
/* survives_restart */ false,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn origin_descriptors_local() {
|
|
let test = WebStorageTest::new();
|
|
test_origin_descriptors(
|
|
test,
|
|
WebStorageType::Local,
|
|
/* survives_restart */ true,
|
|
);
|
|
}
|
|
|
|
fn test_clear_data_for_sites(test: WebStorageTest, storage_type: WebStorageType) {
|
|
let threads = test.threads();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
|
|
// Set a value.
|
|
let _ = test.set_item(storage_type, &url.origin(), "foo", "bar");
|
|
|
|
// Verify length.
|
|
let result = test.length(storage_type, &url.origin());
|
|
assert_eq!(result, 1);
|
|
|
|
// Verify descriptors.
|
|
let descriptors = threads.webstorage_origins(storage_type);
|
|
assert_eq!(descriptors.len(), 1);
|
|
|
|
// Clear site.
|
|
threads.clear_webstorage_for_sites(storage_type, &["example.com"]);
|
|
|
|
// Length should now be zero.
|
|
let result = test.length(storage_type, &url.origin());
|
|
assert_eq!(result, 0);
|
|
|
|
// There should now be no descriptors.
|
|
let descriptors = threads.webstorage_origins(storage_type);
|
|
match storage_type {
|
|
WebStorageType::Session => assert_eq!(descriptors.len(), 0),
|
|
WebStorageType::Local =>
|
|
// TODO: Fix localStorage to not create origin descriptors for
|
|
// read only operations (the length check above).
|
|
{
|
|
assert_eq!(descriptors.len(), 1)
|
|
},
|
|
}
|
|
|
|
// Restart storage threads.
|
|
let test = test.restart();
|
|
let threads = test.threads();
|
|
|
|
// Length should still be zero.
|
|
let result = test.length(storage_type, &url.origin());
|
|
assert_eq!(result, 0);
|
|
|
|
// There should still be no descriptors.
|
|
let descriptors = threads.webstorage_origins(storage_type);
|
|
match storage_type {
|
|
WebStorageType::Session => assert_eq!(descriptors.len(), 0),
|
|
WebStorageType::Local =>
|
|
// TODO: Fix localStorage to not create origin descriptors for
|
|
// read only operations (the length check above).
|
|
{
|
|
assert_eq!(descriptors.len(), 1)
|
|
},
|
|
}
|
|
|
|
// Set a different value.
|
|
let _ = test.set_item(storage_type, &url.origin(), "foo2", "bar2");
|
|
|
|
// Verify the original value doesn't exist.
|
|
let result = test.get_item(storage_type, &url.origin(), "foo");
|
|
assert_eq!(result, None);
|
|
}
|
|
|
|
#[test]
|
|
fn clear_data_for_sites_session() {
|
|
let test = WebStorageTest::new();
|
|
test_clear_data_for_sites(test, WebStorageType::Session);
|
|
}
|
|
|
|
#[test]
|
|
fn clear_data_for_sites_local() {
|
|
let test = WebStorageTest::new();
|
|
test_clear_data_for_sites(test, WebStorageType::Local);
|
|
}
|
|
|
|
#[test]
|
|
fn clear_data_for_sites_local_in_memory() {
|
|
let test = WebStorageTest::new_in_memory();
|
|
test_clear_data_for_sites(test, WebStorageType::Local);
|
|
}
|
|
|
|
#[test]
|
|
fn no_storage_type_conflict() {
|
|
// Ensures that editing session storage does not affect local storage and vice versa.
|
|
let mut test = WebStorageTest::new();
|
|
let url = ServoUrl::parse("https://example.com").unwrap();
|
|
test.set_item(
|
|
WebStorageType::Local,
|
|
&url.origin(),
|
|
"key".into(),
|
|
"local_value".into(),
|
|
)
|
|
.unwrap();
|
|
// Set session storage item.
|
|
test.set_item(
|
|
WebStorageType::Session,
|
|
&url.origin(),
|
|
"key".into(),
|
|
"session_value".into(),
|
|
)
|
|
.unwrap();
|
|
// Shutdown threads to ensure data is cleared from session storage and local storage is loaded from disk
|
|
test = test.restart();
|
|
let result = test.get_item(WebStorageType::Local, &url.origin(), "key".into());
|
|
assert_eq!(result, Some("local_value".into()));
|
|
// Get session storage item.
|
|
let result = test.get_item(WebStorageType::Session, &url.origin(), "key".into());
|
|
assert_eq!(result, None);
|
|
}
|