diff options
Diffstat (limited to 'testing/web-platform/tests/fs/script-tests')
25 files changed, 2624 insertions, 0 deletions
diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js new file mode 100644 index 0000000000..15a0c2a3b5 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-IndexedDB.js @@ -0,0 +1,121 @@ +'use strict'; + +directory_test(async (t, root_dir) => { + const handles = await create_file_system_handles(t, root_dir); + + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store'); + }); + + const value = handles; + + const tx = db.transaction('store', 'readwrite'); + const store = tx.objectStore('store'); + await promiseForRequest(t, store.put(value, 'key')); + const result = await promiseForRequest(t, store.get('key')); + + await promiseForTransaction(t, tx); + + assert_true(Array.isArray(result), 'Result should be an array'); + assert_equals(result.length, value.length); + await assert_equals_cloned_handles(result, value); +}, 'Store handle in IndexedDB and read from pending transaction.'); + +directory_test(async (t, root_dir) => { + const handles = await create_file_system_handles(t, root_dir); + + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store'); + }); + + const value = handles; + + let tx = db.transaction('store', 'readwrite'); + let store = tx.objectStore('store'); + await promiseForRequest(t, store.put(value, 'key')); + await promiseForTransaction(t, tx); + + tx = db.transaction('store', 'readonly'); + store = tx.objectStore('store'); + const result = await promiseForRequest(t, store.get('key')); + await promiseForTransaction(t, tx); + + assert_true(Array.isArray(result), 'Result should be an array'); + assert_equals(result.length, value.length); + await assert_equals_cloned_handles(result, value); +}, 'Store handle in IndexedDB and read from new transaction.'); + +directory_test(async (t, root_dir) => { + const handles = await create_file_system_handles(t, root_dir); + + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store'); + }); + + const value = {handles, blob: new Blob(["foobar"])}; + + let tx = db.transaction('store', 'readwrite'); + let store = tx.objectStore('store'); + await promiseForRequest(t, store.put(value, 'key')); + await promiseForTransaction(t, tx); + + tx = db.transaction('store', 'readonly'); + store = tx.objectStore('store'); + const result = await promiseForRequest(t, store.get('key')); + await promiseForTransaction(t, tx); + + assert_true(Array.isArray(result.handles), 'Result should be an array'); + assert_equals(result.handles.length, value.handles.length); + await assert_equals_cloned_handles(result.handles, value.handles); + + assert_equals(await result.blob.text(), await value.blob.text()); +}, 'Store handles and blobs in IndexedDB.'); + +directory_test(async (t, root_dir) => { + const handles = await create_file_system_handles(t, root_dir); + + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store'); + }); + + const value = handles; + + let tx = db.transaction('store', 'readwrite'); + let store = tx.objectStore('store'); + await promiseForRequest(t, store.put(value, 'key')); + await promiseForTransaction(t, tx); + + tx = db.transaction('store', 'readonly'); + store = tx.objectStore('store'); + let cursor_request = store.openCursor(); + await requestWatcher(t, cursor_request).wait_for('success'); + const result = cursor_request.result.value; + await promiseForTransaction(t, tx); + + assert_true(Array.isArray(result), 'Result should be an array'); + assert_equals(result.length, value.length); + await assert_equals_cloned_handles(result, value); +}, 'Store handle in IndexedDB and read using a cursor.'); + +directory_test(async (t, root_dir) => { + const handles = await create_file_system_handles(t, root_dir); + + const db = await createDatabase(t, db => { + const store = db.createObjectStore('store', {keyPath: 'key'}); + }); + + const value = handles; + let tx = db.transaction('store', 'readwrite'); + let store = tx.objectStore('store'); + await promiseForRequest(t, store.put({key: 'key', value})); + await promiseForTransaction(t, tx); + + tx = db.transaction('store', 'readonly'); + store = tx.objectStore('store'); + const result = await promiseForRequest(t, store.get('key')); + await promiseForTransaction(t, tx); + + assert_true(Array.isArray(result.value), 'Result should be an array'); + assert_equals(result.value.length, value.length); + await assert_equals_cloned_handles(result.value, value); +}, 'Store handle in IndexedDB using inline keys.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js new file mode 100644 index 0000000000..98261995f9 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-buckets.js @@ -0,0 +1,19 @@ +'use strict'; + +directory_test(async (t, root_dir) => { + const inboxBucket = await navigator.storageBuckets.open('inbox'); + const inboxRootDir = await inboxBucket.getDirectory(); + + assert_false(await inboxRootDir.isSameEntry(root_dir)); + + const handle1 = await createEmptyFile(t, 'mtime.txt', inboxRootDir); + const handle2 = await inboxRootDir.getFileHandle('mtime.txt'); + assert_true(await handle1.isSameEntry(handle2)); +}, 'isSameEntry works as expected with buckets'); + +directory_test(async (t, root_dir) => { + const inboxBucket = await navigator.storageBuckets.open('inbox'); + await navigator.storageBuckets.delete('inbox'); + const directoryPromise = inboxBucket.getDirectory(); + await promise_rejects_dom(t, 'InvalidStateError', directoryPromise); +}, 'getDirectory promise rejects if bucket has been deleted'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js new file mode 100644 index 0000000000..ee0cd5e349 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-getUniqueId.js @@ -0,0 +1,89 @@ +'use strict'; + +directory_test(async (t, root_dir) => { + assert_equals(await root_dir.getUniqueId(), await root_dir.getUniqueId()); + + const subdir = await createDirectory(t, 'subdir-name', root_dir); + assert_equals(await subdir.getUniqueId(), await subdir.getUniqueId()); +}, 'identical directory handles return the same ID'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + + assert_not_equals(await root_dir.getUniqueId(), await subdir.getUniqueId()); +}, 'different directories return different IDs'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + const subdir2 = await root_dir.getDirectoryHandle('subdir-name'); + + assert_equals(await subdir.getUniqueId(), await subdir2.getUniqueId()); +}, 'different handles for the same directory return the same ID'); + +directory_test(async (t, root_dir) => { + const handle = await createEmptyFile(t, 'foo.txt', root_dir); + + assert_equals(await handle.getUniqueId(), await handle.getUniqueId()); +}, 'identical file handles return the same unique ID'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'foo.txt', root_dir); + const handle2 = await createEmptyFile(t, 'bar.txt', root_dir); + + assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId()); +}, 'different files return different IDs'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'foo.txt', root_dir); + const handle2 = await root_dir.getFileHandle('foo.txt'); + + assert_equals(await handle1.getUniqueId(), await handle2.getUniqueId()); +}, 'different handles for the same file return the same ID'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'foo.txt', root_dir); + const subdir = await createDirectory(t, 'subdir-name', root_dir); + const handle2 = await createEmptyFile(t, 'foo.txt', subdir); + + assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId()); +}, 'two files of the same name in different directories return different IDs'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'foo.txt', root_dir); + const handle2 = await createDirectory(t, 'subdir-name', root_dir); + + assert_not_equals(await handle1.getUniqueId(), await handle2.getUniqueId()); +}, 'a file and a directory return different IDs'); + +directory_test(async (t, root_dir) => { + const file_handle = await createEmptyFile(t, 'foo', root_dir); + const file_id = await file_handle.getUniqueId(); + + // Remove the file. + await root_dir.removeEntry('foo'); + + // Create a directory of the same name and path. + const dir_handle = await createDirectory(t, 'foo', root_dir); + assert_not_equals(await dir_handle.getUniqueId(), file_id); +}, 'a file and a directory of the same path return different IDs'); + +directory_test(async (t, root_dir) => { + const handle = await createEmptyFile(t, 'foo.txt', root_dir); + const id_before = await handle.getUniqueId(); + + // Write to the file. The unique ID should not change. + const writable = await cleanup_writable(t, await handle.createWritable()); + await writable.write("blah"); + await writable.close(); + + assert_equals(await handle.getUniqueId(), id_before); +}, 'unique ID of a file handle does not change after writes'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + + const UUIDRegex = + /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/ + assert_true(UUIDRegex.test(await root_dir.getUniqueId())); + assert_true(UUIDRegex.test(await subdir.getUniqueId())); +}, 'unique ID is in GUID version 4 format'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js new file mode 100644 index 0000000000..e3b6d1891e --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-isSameEntry.js @@ -0,0 +1,107 @@ +'use strict'; + +directory_test(async (t, root_dir) => { + assert_true(await root_dir.isSameEntry(root_dir)); + + const subdir = await createDirectory(t, 'subdir-name', root_dir); + assert_true(await subdir.isSameEntry(subdir)); +}, 'isSameEntry for identical directory handles returns true'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + + assert_false(await root_dir.isSameEntry(subdir)); + assert_false(await subdir.isSameEntry(root_dir)); +}, 'isSameEntry for different directories returns false'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + const subdir2 = await root_dir.getDirectoryHandle('subdir-name'); + + assert_true(await subdir.isSameEntry(subdir2)); + assert_true(await subdir2.isSameEntry(subdir)); +}, 'isSameEntry for different handles for the same directory'); + +directory_test(async (t, root_dir) => { + const handle = await createEmptyFile(t, 'mtime.txt', root_dir); + + assert_true(await handle.isSameEntry(handle)); +}, 'isSameEntry for identical file handles returns true'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir); + const handle2 = await createEmptyFile(t, 'foo.txt', root_dir); + + assert_false(await handle1.isSameEntry(handle2)); + assert_false(await handle2.isSameEntry(handle1)); +}, 'isSameEntry for different files returns false'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir); + const handle2 = await root_dir.getFileHandle('mtime.txt'); + + assert_true(await handle1.isSameEntry(handle2)); + assert_true(await handle2.isSameEntry(handle1)); +}, 'isSameEntry for different handles for the same file'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir); + const subdir = await createDirectory(t, 'subdir-name', root_dir); + const handle2 = await createEmptyFile(t, 'mtime.txt', subdir); + + assert_false(await handle1.isSameEntry(handle2)); + assert_false(await handle2.isSameEntry(handle1)); +}, 'isSameEntry comparing a file to a file in a different directory returns false'); + +directory_test(async (t, root_dir) => { + const handle1 = await createEmptyFile(t, 'mtime.txt', root_dir); + const handle2 = await createDirectory(t, 'subdir-name', root_dir); + + assert_false(await handle1.isSameEntry(handle2)); + assert_false(await handle2.isSameEntry(handle1)); +}, 'isSameEntry comparing a file to a directory returns false'); + +directory_test(async (t, root_dir) => { + const filename = 'foo'; + const handle1 = await createEmptyFile(t, filename, root_dir); + // Remove the file and create a new file of the same path. + await root_dir.removeEntry(filename); + const handle2 = await createEmptyFile(t, filename, root_dir); + + assert_true( + await handle1.isSameEntry(handle2), + 'two file handles pointing at the same path should be considered the same entry'); + assert_true( + await handle2.isSameEntry(handle1), + 'two file handles pointing at the same path should be considered the same entry'); +}, 'isSameEntry comparing two files pointing to the same path returns true'); + +directory_test(async (t, root_dir) => { + const filename = 'foo'; + const handle1 = await createDirectory(t, filename, root_dir); + // Remove the directory and create a new directory of the same path. + await root_dir.removeEntry(filename); + const handle2 = await createDirectory(t, filename, root_dir); + + assert_true( + await handle1.isSameEntry(handle2), + 'two directory handles pointing at the same path should be considered the same entry'); + assert_true( + await handle2.isSameEntry(handle1), + 'two directory handles pointing at the same path should be considered the same entry'); +}, 'isSameEntry comparing two directories pointing to the same path returns true'); + +directory_test(async (t, root_dir) => { + const filename = 'foo'; + const dir_handle = await createDirectory(t, filename, root_dir); + // Remove the directory and create a file of the same path. + await root_dir.removeEntry(filename); + const file_handle = await createEmptyFile(t, filename, root_dir); + + assert_false( + await dir_handle.isSameEntry(file_handle), + 'a file and directory handle pointing at the same path should not be considered the same entry'); + assert_false( + await file_handle.isSameEntry(dir_handle), + 'a file and directory handle pointing at the same path should not be considered the same entry'); +}, 'isSameEntry comparing a file to a directory of the same path returns false'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js new file mode 100644 index 0000000000..681037db2f --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-BroadcastChannel.js @@ -0,0 +1,82 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js +// /service-workers/service-worker/resources/test-helpers.sub.js + +// Sets up a new broadcast channel in |target|. Posts a message instructing +// |target| to open the broadcast channel using |broadcast_channel_name|. +async function create_broadcast_channel( + test, broadcast_channel_name, receiver, target, target_origin) { + target.postMessage( + { type: 'create-broadcast-channel', broadcast_channel_name }, + { targetOrigin: target_origin }); + const event_watcher = new EventWatcher(test, receiver, 'message'); + + // Wait until |target| is listening to the broad cast channel. + const message_event = await event_watcher.wait_for('message'); + assert_equals(message_event.data.type, 'broadcast-channel-created', + 'The message target must receive a "broadcast-channel-created" message ' + + 'response.'); +} + +// This test is very similar to 'FileSystemBaseHandle-postMessage.js'. It +// starts by creating three message targets for the broadcast channel: +// an iframe, dedicated worker and a service worker. After setup, an array +// of FileSystemHandles is sent across the broadcast channel. The test +// expects three responses -- one from each message target. +directory_test(async (t, root) => { + const broadcast_channel_name = 'file-system-file-handle-channel'; + const broadcast_channel = new BroadcastChannel(broadcast_channel_name); + const broadcast_channel_event_watcher = + new EventWatcher(t, broadcast_channel, 'message'); + + const iframe = await add_iframe(t, { src: kDocumentMessageTarget }); + await create_broadcast_channel( + t, broadcast_channel_name, self, iframe.contentWindow, '*'); + + const scope = `${kServiceWorkerMessageTarget}` + + '?post-message-to-broadcast-channel-with-file-handle'; + + const registration = await create_service_worker( + t, kServiceWorkerMessageTarget, scope); + + await create_broadcast_channel( + t, broadcast_channel_name, + navigator.serviceWorker, registration.installing); + + const dedicated_worker = + create_dedicated_worker(t, kDedicatedWorkerMessageTarget); + + await create_broadcast_channel( + t, broadcast_channel_name, dedicated_worker, dedicated_worker); + + const handles = await create_file_system_handles(t, root); + + broadcast_channel.postMessage( + { type: 'receive-file-system-handles', cloned_handles: handles }); + + const expected_response_count = 3; + const responses = []; + for (let i = 0; i < expected_response_count; ++i) { + const message_event = + await broadcast_channel_event_watcher.wait_for('message'); + responses.push(message_event.data); + } + + const expected_serialized_handles = await serialize_handles(handles); + + for (let i = 0; i < responses.length; ++i) { + assert_equals(responses[i].type, 'receive-serialized-file-system-handles', + 'The test runner must receive a "serialized-file-system-handles" ' + + `message response. Actual response: ${responses[i]}`); + + assert_equals_serialized_handles( + responses[i].serialized_handles, expected_serialized_handles); + + await assert_equals_cloned_handles(responses[i].cloned_handles, handles); + } +}, 'Send and receive messages using a broadcast channel in an iframe, ' + +'dedicated worker and service worker.');
\ No newline at end of file diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js new file mode 100644 index 0000000000..7c97a7da48 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-Error.js @@ -0,0 +1,244 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js +// /common/get-host-info.sub.js +// /service-workers/service-worker/resources/test-helpers.sub.js + +// Define URL constants for cross origin windows. +const kRemoteOrigin = get_host_info().HTTPS_REMOTE_ORIGIN; +const kRemoteOriginDocumentMessageTarget = `${kRemoteOrigin}${base_path()}` + + kDocumentMessageTarget; + +// Sending a FileSystemHandle to a cross origin |target| through postMessage() +// must dispatch the 'messageerror' event. +// +// This test sends a FileSystemHandle to |target|. |target| responds with a +// serialized MessageEvent from the 'messageerror' event, allowing the test +// runner to verify MessageEvent properties. +async function do_send_message_error_test( + test, + root_dir, + receiver, + target, + target_origin, + // False when the MessageEvent's source is null. + expected_has_source, + // The origin of MessageEvents received by |target|. + expected_origin) { + const message_watcher = new EventWatcher(test, receiver, 'message'); + + // Send a file to |target|. + const file = await createFileWithContents( + test, 'test-error-file', 'test-error-file-contents', root_dir); + target.postMessage( + { type: 'receive-file-system-handles', cloned_file_system_handles: [file] }, + { targetOrigin: target_origin }); + + // Wait for |target| to respond with results. + let message_event = await message_watcher.wait_for('message'); + const first_response = message_event.data; + assert_equals(first_response.type, 'serialized-message-error', + 'The test runner must receive a "serialized-message-error" message ' + + 'in response to a FileSystemFileHandle message.'); + + // Verify the results. + assert_equals_serialized_message_error_event( + first_response.serialized_message_error_event, + expected_origin, expected_has_source); + + // Send a directory to |target|. + const directory = await createDirectory( + test, 'test-error-directory', root_dir); + + target.postMessage( + { + type: 'receive-file-system-handles', + cloned_file_system_handles: [directory] + }, { targetOrigin: target_origin }); + + // Wait for |target| to respond with results. + message_event = await message_watcher.wait_for('message'); + const second_response = message_event.data; + assert_equals(second_response.type, 'serialized-message-error', + 'The test runner must receive a "serialized-message-error" message ' + + 'response to a FileSystemDirectoryHandle message.'); + + // Verify the results. + assert_equals_serialized_message_error_event( + second_response.serialized_message_error_event, + expected_origin, expected_has_source); +} + +// This test receives a FileSystemHandle from |target|. This test runner +// must dispatch the 'messageerror' event after receiving a handle from target. +async function do_receive_message_error_test( + test, + receiver, + target, + target_origin, + // False when the MessageEvent's source is null. + expected_has_source, + // The origin of MessageEvents received by this test runner. + expected_origin) { + const error_watcher = new EventWatcher(test, receiver, 'messageerror'); + + // Receive a file from |target|. + target.postMessage( + { type: 'create-file' }, { targetOrigin: target_origin }); + const first_error = await error_watcher.wait_for('messageerror'); + const serialized_first_error = serialize_message_error_event(first_error); + assert_equals_serialized_message_error_event( + serialized_first_error, expected_origin, expected_has_source); + + // Receive a directory from |target|. + target.postMessage( + { type: 'create-directory' }, { targetOrigin: target_origin }); + const second_error = await error_watcher.wait_for('messageerror'); + const serialized_second_error = serialize_message_error_event(second_error); + assert_equals_serialized_message_error_event( + serialized_second_error, expected_origin, expected_has_source); +} + +// Performs the send message error test followed by the receive message error +// test. +async function do_send_and_receive_message_error_test( + test, + root_dir, + receiver, + target, + target_origin, + // False when the MessageEvent's source is null. + expected_has_source, + // The origin of MessageEvents received by |target|. + expected_origin, + // The origin of MessageEvents received by this test runner. + expected_remote_origin) { + await do_send_message_error_test( + test, root_dir, receiver, target, target_origin, expected_has_source, + expected_origin); + await do_receive_message_error_test( + test, receiver, target, target_origin, expected_has_source, + expected_remote_origin); +} + +// Runs the same test as do_send_message_error_test(), but uses a MessagePort. +// This test starts by establishing a message channel between the test runner +// and |target|. +async function do_send_message_port_error_test( + test, root_dir, target, target_origin) { + const message_port = create_message_channel(target, target_origin); + await do_send_message_error_test( + test, root_dir, /*receiver=*/message_port, /*target=*/message_port, + /*target_origin=*/undefined, /*expected_has_source=*/false, + /*expected_origin=*/'', /*expected_remote_origin=*/''); +} + +// Runs the same test as do_receive_message_error_test(), but uses a MessagePort. +async function do_receive_message_port_error_test( + test, target, target_origin) { + const message_port = create_message_channel(target, target_origin); + await do_receive_message_error_test( + test, /*receiver=*/message_port, /*target=*/message_port, + /*target_origin=*/undefined, /*expected_has_source=*/false, + /*expected_origin=*/''); +} + +// Runs the same test as do_send_and_receive_message_error_test(), but uses a +// MessagePort. +async function do_send_and_receive_message_port_error_test( + test, root_dir, target, target_origin) { + await do_send_message_port_error_test( + test, root_dir, target, target_origin); + await do_receive_message_port_error_test( + test, target, target_origin); +} + +directory_test(async (t, root_dir) => { + const iframe = await add_iframe( + t, { src: kRemoteOriginDocumentMessageTarget }); + await do_send_and_receive_message_error_test( + t, root_dir, /*receiver=*/self, /*target=*/iframe.contentWindow, + /*target_origin=*/'*', /*expected_has_source=*/true, + /*expected_origin=*/location.origin, + /*expected_remote_origin=*/kRemoteOrigin); +}, 'Fail to send and receive messages using a cross origin iframe.'); + +directory_test(async (t, root_dir) => { + const iframe = await add_iframe(t, { src: kRemoteOriginDocumentMessageTarget }); + await do_send_and_receive_message_port_error_test( + t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*'); +}, 'Fail to send and receive messages using a cross origin message port in ' + +'an iframe.'); + +directory_test(async (t, root_dir) => { + const iframe = await add_iframe( + t, { src: kDocumentMessageTarget, sandbox: 'allow-scripts' }); + + await do_send_message_error_test( + t, root_dir, /*receiver=*/self, /*target=*/iframe.contentWindow, + /*target_origin=*/'*', /*expected_has_source*/true, + /*expected_origin=*/location.origin); +}, 'Fail to send to a sandboxed iframe.'); + +directory_test(async (t, root_dir) => { + const iframe = await add_iframe( + t, { src: kDocumentMessageTarget, sandbox: 'allow-scripts' }); + await do_send_message_port_error_test( + t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*'); +}, 'Fail to send messages using a message port to a sandboxed ' + +'iframe.'); + +directory_test(async (t, root_dir) => { + const iframe_data_uri = await create_message_target_data_uri(t); + const iframe = await add_iframe(t, { src: iframe_data_uri }); + await do_send_message_error_test(t, root_dir, /*receiver=*/self, + /*target=*/iframe.contentWindow, /*target_origin=*/'*', + /*expected_has_source*/true, /*expected_origin=*/location.origin); + // Do not test receiving FileSystemHandles from the data URI iframe. Data URI + // iframes are insecure and do not expose the File System APIs. +}, 'Fail to send messages to a data URI iframe.'); + +directory_test(async (t, root_dir) => { + const iframe_data_uri = await create_message_target_data_uri(t); + const iframe = await add_iframe(t, { src: iframe_data_uri }); + await do_send_message_port_error_test( + t, root_dir, /*target=*/iframe.contentWindow, /*target_origin=*/'*'); +}, 'Fail to send messages using a message port in a data URI iframe.'); + +directory_test(async (t, root_dir) => { + const child_window = await open_window(t, kRemoteOriginDocumentMessageTarget); + await do_send_and_receive_message_error_test( + t, root_dir, /*receiver=*/self, /*target=*/child_window, /*target_origin=*/'*', + /*expected_has_source=*/true, /*expected_origin=*/location.origin, + /*expected_remote_origin=*/kRemoteOrigin); +}, 'Fail to send and receive messages using a cross origin window.'); + +directory_test(async (t, root_dir) => { + const child_window = await open_window(t, kRemoteOriginDocumentMessageTarget); + await do_send_message_port_error_test( + t, root_dir, /*target=*/child_window, /*target_origin=*/'*'); +}, 'Fail to send and receive messages using a cross origin message port in ' + +'a window.'); + +directory_test(async (t, root_dir) => { + const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` + + ', sandbox allow-scripts)'; + const child_window = await open_window(t, url); + await do_send_message_error_test( + t, root_dir, /*receiver=*/self, /*target=*/child_window, + /*target_origin=*/'*', /*expected_has_source*/true, + /*expected_origin=*/location.origin); +}, 'Fail to send messages to a sandboxed window.'); + +directory_test(async (t, root_dir) => { + const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` + + ', sandbox allow-scripts)'; + const child_window = await open_window(t, url); + await do_send_message_port_error_test( + t, root_dir, /*target=*/child_window, /*target_origin=*/'*'); +}, 'Fail to send messages using a message port to a sandboxed ' + +'window.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js new file mode 100644 index 0000000000..b70b2992c6 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-frames.js @@ -0,0 +1,44 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js + +directory_test( + async (t, root_dir) => { + const iframe = await add_iframe(t, {src: kDocumentMessageTarget}); + await do_message_port_test( + t, root_dir, /*target=*/ iframe.contentWindow, + /*target_origin=*/ '*'); + }, + 'Send and receive messages using a message port in a same origin ' + + 'iframe.'); + +directory_test( + async (t, root_dir) => { + const iframe = await add_iframe(t, { + src: kDocumentMessageTarget, + sandbox: 'allow-scripts allow-same-origin' + }); + await do_message_port_test( + t, root_dir, /*target=*/ iframe.contentWindow, + /*target_origin=*/ '*'); + }, + 'Send and receive messages using a message port in a sandboxed same ' + + 'origin iframe.'); + +directory_test(async (t, root_dir) => { + const blob_url = await create_message_target_blob_url(t); + const iframe = await add_iframe(t, {src: blob_url}); + await do_message_port_test( + t, root_dir, /*target=*/ iframe.contentWindow, /*target_origin=*/ '*'); +}, 'Send and receive messages using a message port in a blob iframe.'); + +directory_test(async (t, root_dir) => { + const iframe_html = await create_message_target_html_without_subresources(t); + const iframe = await add_iframe(t, {srcdoc: iframe_html}); + await do_message_port_test( + t, root_dir, /*target=*/ iframe.contentWindow, /*target_origin=*/ '*'); +}, 'Send and receive messages using a message port in an iframe srcdoc.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js new file mode 100644 index 0000000000..dceb250ebb --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-windows.js @@ -0,0 +1,35 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js + +directory_test( + async (t, root_dir) => { + const child_window = await open_window(t, kDocumentMessageTarget); + await do_message_port_test( + t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*'); + }, + 'Send and receive messages using a message port in a same origin ' + + 'window.'); + +directory_test(async (t, root_dir) => { + const blob_url = await create_message_target_blob_url(t); + const child_window = await open_window(t, blob_url); + await do_message_port_test( + t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*'); +}, 'Send and receive messages using a message port in a blob window.'); + +directory_test( + async (t, root_dir) => { + const url = + `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` + + ', sandbox allow-scripts allow-same-origin)'; + const child_window = await open_window(t, url); + await do_message_port_test( + t, root_dir, /*target=*/ child_window, /*target_origin=*/ '*'); + }, + 'Send and receive messages using a message port in a sandboxed same ' + + 'origin window.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js new file mode 100644 index 0000000000..b386527dbd --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-MessagePort-workers.js @@ -0,0 +1,40 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js +// /service-workers/service-worker/resources/test-helpers.sub.js + +directory_test( + async (t, root_dir) => { + const dedicated_worker = + create_dedicated_worker(t, kDedicatedWorkerMessageTarget); + await do_message_port_test(t, root_dir, /*target=*/ dedicated_worker); + }, + 'Send and receive messages using a message port in a dedicated ' + + 'worker.'); + +directory_test( + async (t, root_dir) => { + const scope = `${kServiceWorkerMessageTarget}` + + '?post-message-to-message-port-with-file-handle'; + const registration = + await create_service_worker(t, kServiceWorkerMessageTarget, scope); + await do_message_port_test( + t, root_dir, /*target=*/ registration.installing); + }, + 'Send and receive messages using a message port in a service ' + + 'worker.'); + +if (self.SharedWorker !== undefined) { + directory_test( + async (t, root_dir) => { + const shared_worker = new SharedWorker(kSharedWorkerMessageTarget); + shared_worker.port.start(); + await do_message_port_test(t, root_dir, /*target=*/ shared_worker.port); + }, + 'Send and receive messages using a message port in a shared ' + + ' worker.'); +} diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js new file mode 100644 index 0000000000..1e77b89d77 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-frames.js @@ -0,0 +1,40 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js + +directory_test(async (t, root_dir) => { + const iframe = await add_iframe(t, {src: kDocumentMessageTarget}); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow, + /*target_origin=*/ '*'); +}, 'Send and receive messages using a same origin iframe.'); + +directory_test(async (t, root_dir) => { + const iframe = await add_iframe(t, { + src: kDocumentMessageTarget, + sandbox: 'allow-scripts allow-same-origin' + }); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow, + /*target_origin=*/ '*'); +}, 'Send and receive messages using a sandboxed same origin iframe.'); + +directory_test(async (t, root_dir) => { + const blob_url = await create_message_target_blob_url(t); + const iframe = await add_iframe(t, {src: blob_url}); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow, + /*target_origin=*/ '*'); +}, 'Send and receive messages using a blob iframe.'); + +directory_test(async (t, root_dir) => { + const iframe_html = await create_message_target_html_without_subresources(t); + const iframe = await add_iframe(t, {srcdoc: iframe_html}); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ iframe.contentWindow, + /*target_origin=*/ '*'); +}, 'Send and receive messages using an iframe srcdoc.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js new file mode 100644 index 0000000000..798d458534 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-windows.js @@ -0,0 +1,31 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js + +directory_test(async (t, root_dir) => { + const child_window = await open_window(t, kDocumentMessageTarget); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ child_window, + /*target_origin=*/ '*'); +}, 'Send and receive messages using a same origin window.'); + +directory_test(async (t, root_dir) => { + const blob_url = await create_message_target_blob_url(t); + const child_window = await open_window(t, blob_url); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ child_window, + /*target_origin=*/ '*'); +}, 'Send and receive messages using a blob window.'); + +directory_test(async (t, root_dir) => { + const url = `${kDocumentMessageTarget}?pipe=header(Content-Security-Policy` + + ', sandbox allow-scripts allow-same-origin)'; + const child_window = await open_window(t, url); + await do_post_message_test( + t, root_dir, /*receiver=*/ self, /*target=*/ child_window, + /*target_origin=*/ '*'); +}, 'Send and receive messages using a sandboxed same origin window.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js new file mode 100644 index 0000000000..dbd8e5754d --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-postMessage-workers.js @@ -0,0 +1,35 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/messaging-blob-helpers.js +// /fs/resources/messaging-serialize-helpers.js +// /fs/resources/test-helpers.js +// /service-workers/service-worker/resources/test-helpers.sub.js + +directory_test(async (t, root_dir) => { + const dedicated_worker = + create_dedicated_worker(t, kDedicatedWorkerMessageTarget); + await do_post_message_test( + t, root_dir, /*receiver=*/ dedicated_worker, + /*target=*/ dedicated_worker); +}, 'Send and receive messages using a dedicated worker.'); + +directory_test(async (t, root_dir) => { + const scope = `${kServiceWorkerMessageTarget}?post-message-with-file-handle`; + const registration = + await create_service_worker(t, kServiceWorkerMessageTarget, scope); + await do_post_message_test( + t, root_dir, /*receiver=*/ navigator.serviceWorker, + /*target=*/ registration.installing); +}, 'Send and receive messages using a service worker.'); + +if (self.SharedWorker !== undefined) { + directory_test(async (t, root_dir) => { + const shared_worker = new SharedWorker(kSharedWorkerMessageTarget); + shared_worker.port.start(); + await do_post_message_test( + t, root_dir, /*receiver=*/ shared_worker.port, + /*target=*/ shared_worker.port); + }, 'Send and receive messages using a shared worker.'); +} diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js new file mode 100644 index 0000000000..021576310b --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemBaseHandle-remove.js @@ -0,0 +1,105 @@ +'use strict'; + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await handle.remove(); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); + await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle)); +}, 'remove() to remove a file'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await handle.remove(); + + await promise_rejects_dom(t, 'NotFoundError', handle.remove()); +}, 'remove() on an already removed file should fail'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await dir.remove(); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); + await promise_rejects_dom(t, 'NotFoundError', getSortedDirectoryEntries(dir)); +}, 'remove() to remove an empty directory'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await dir.remove(); + + await promise_rejects_dom(t, 'NotFoundError', dir.remove()); +}, 'remove() on an already removed directory should fail'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + t.add_cleanup(() => root.removeEntry('dir-to-remove', {recursive: true})); + await createEmptyFile(t, 'file-in-dir', dir); + + await promise_rejects_dom(t, 'InvalidModificationError', dir.remove()); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-to-remove/']); + assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']); +}, 'remove() on a non-empty directory should fail'); + +directory_test(async (t, root) => { + // root + // ├──file-to-keep + // ├──dir-to-remove + // ├── file0 + // ├── dir1-in-dir + // │  └── file1 + // └── dir2 + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await createEmptyFile(t, 'file0', dir); + const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir); + await createEmptyFile(t, 'file1', dir1_in_dir); + await createDirectory(t, 'dir2-in-dir', dir); + + await dir.remove({recursive: true}); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); +}, 'remove() on a directory recursively should delete all sub-items'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await handle.remove({recursive: true}); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); + await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle)); +}, 'remove() on a file should ignore the recursive option'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + + const writable = await cleanup_writable(t, await handle.createWritable()); + await promise_rejects_dom(t, 'NoModificationAllowedError', handle.remove()); + + await writable.close(); + assert_array_equals( + await getSortedDirectoryEntries(root), + ['file-to-keep', 'file-to-remove']); + + await handle.remove(); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); + await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle)); +}, 'remove() while the file has an open writable fails'); + +promise_test(async (t) => { + const root = await navigator.storage.getDirectory(); + await root.getFileHandle('file.txt', {create: true}); + assert_array_equals(await getSortedDirectoryEntries(root), ['file.txt']); + + await root.remove(); + + // Creates a fresh sandboxed file system. + const newRoot = await navigator.storage.getDirectory(); + assert_array_equals(await getSortedDirectoryEntries(newRoot), []); +}, 'can remove the root of a sandbox file system'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js new file mode 100644 index 0000000000..6a63edecc5 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getDirectoryHandle.js @@ -0,0 +1,117 @@ +'use strict'; + +directory_test(async (t, root) => { + await promise_rejects_dom( + t, 'NotFoundError', root.getDirectoryHandle('non-existing-dir')); +}, 'getDirectoryHandle(create=false) rejects for non-existing directories'); + +directory_test(async (t, root) => { + const handle = + await root.getDirectoryHandle('non-existing-dir', {create: true}); + t.add_cleanup(() => root.removeEntry('non-existing-dir', {recursive: true})); + + assert_equals(handle.kind, 'directory'); + assert_equals(handle.name, 'non-existing-dir'); + assert_equals(await getDirectoryEntryCount(handle), 0); + assert_array_equals( + await getSortedDirectoryEntries(root), ['non-existing-dir/']); +}, 'getDirectoryHandle(create=true) creates an empty directory'); + +directory_test(async (t, root) => { + const existing_handle = + await root.getDirectoryHandle('dir-with-contents', {create: true}); + t.add_cleanup(() => root.removeEntry('dir-with-contents', {recursive: true})); + const file_handle = await createEmptyFile(t, 'test-file', existing_handle); + + const handle = + await root.getDirectoryHandle('dir-with-contents', {create: false}); + + assert_equals(handle.kind, 'directory'); + assert_equals(handle.name, 'dir-with-contents'); + assert_array_equals(await getSortedDirectoryEntries(handle), ['test-file']); +}, 'getDirectoryHandle(create=false) returns existing directories'); + +directory_test(async (t, root) => { + const existing_handle = + await root.getDirectoryHandle('dir-with-contents', {create: true}); + t.add_cleanup(() => root.removeEntry('dir-with-contents', {recursive: true})); + const file_handle = + await existing_handle.getFileHandle('test-file', {create: true}); + + const handle = + await root.getDirectoryHandle('dir-with-contents', {create: true}); + + assert_equals(handle.kind, 'directory'); + assert_equals(handle.name, 'dir-with-contents'); + assert_array_equals(await getSortedDirectoryEntries(handle), ['test-file']); +}, 'getDirectoryHandle(create=true) returns existing directories without erasing'); + +directory_test(async (t, root) => { + await createEmptyFile(t, 'file-name', root); + + await promise_rejects_dom( + t, 'TypeMismatchError', root.getDirectoryHandle('file-name')); + await promise_rejects_dom( + t, 'TypeMismatchError', + root.getDirectoryHandle('file-name', {create: false})); + await promise_rejects_dom( + t, 'TypeMismatchError', + root.getDirectoryHandle('file-name', {create: true})); +}, 'getDirectoryHandle() when a file already exists with the same name'); + +directory_test(async (t, dir) => { + await promise_rejects_js( + t, TypeError, dir.getDirectoryHandle('', {create: true})); + await promise_rejects_js( + t, TypeError, dir.getDirectoryHandle('', {create: false})); +}, 'getDirectoryHandle() with empty name'); + +directory_test(async (t, dir) => { + await promise_rejects_js( + t, TypeError, dir.getDirectoryHandle(kCurrentDirectory)); + await promise_rejects_js( + t, TypeError, dir.getDirectoryHandle(kCurrentDirectory, {create: true})); +}, `getDirectoryHandle() with "${kCurrentDirectory}" name`); + +directory_test(async (t, dir) => { + const subdir = await createDirectory(t, 'subdir-name', /*parent=*/ dir); + + await promise_rejects_js( + t, TypeError, subdir.getDirectoryHandle(kParentDirectory)); + await promise_rejects_js( + t, TypeError, + subdir.getDirectoryHandle(kParentDirectory, {create: true})); +}, `getDirectoryHandle() with "${kParentDirectory}" name`); + +directory_test(async (t, dir) => { + const first_subdir_name = 'first-subdir-name'; + const first_subdir = + await createDirectory(t, first_subdir_name, /*parent=*/ dir); + + const second_subdir_name = 'second-subdir-name'; + const second_subdir = + await createDirectory(t, second_subdir_name, /*parent=*/ first_subdir); + + for (let i = 0; i < kPathSeparators.length; ++i) { + const path_with_separator = + `${first_subdir_name}${kPathSeparators[i]}${second_subdir_name}`; + await promise_rejects_js( + t, TypeError, dir.getDirectoryHandle(path_with_separator), + `getDirectoryHandle() must reject names containing "${ + kPathSeparators[i]}"`); + } +}, 'getDirectoryHandle(create=false) with a path separator when the directory exists'); + +directory_test(async (t, dir) => { + const subdir_name = 'subdir-name'; + const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir); + + for (let i = 0; i < kPathSeparators.length; ++i) { + const path_with_separator = `${subdir_name}${kPathSeparators[i]}file_name`; + await promise_rejects_js( + t, TypeError, + dir.getDirectoryHandle(path_with_separator, {create: true}), + `getDirectoryHandle(true) must reject names containing "${ + kPathSeparators[i]}"`); + } +}, 'getDirectoryHandle(create=true) with a path separator'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js new file mode 100644 index 0000000000..840e85b436 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-getFileHandle.js @@ -0,0 +1,145 @@ +'use strict'; + +directory_test(async (t, dir) => { + await promise_rejects_dom( + t, 'NotFoundError', dir.getFileHandle('non-existing-file')); +}, 'getFileHandle(create=false) rejects for non-existing files'); + +directory_test(async (t, dir) => { + const handle = await dir.getFileHandle('non-existing-file', {create: true}); + t.add_cleanup(() => dir.removeEntry('non-existing-file')); + + assert_equals(handle.kind, 'file'); + assert_equals(handle.name, 'non-existing-file'); + assert_equals(await getFileSize(handle), 0); + assert_equals(await getFileContents(handle), ''); +}, 'getFileHandle(create=true) creates an empty file for non-existing files'); + +directory_test(async (t, dir) => { + var name = ''; + // test the ascii characters -- start after the non-character ASCII values, exclude DEL + for (let i = 32; i < 127; i++) { + // Path separators are disallowed + let disallow = false; + for (let j = 0; j < kPathSeparators.length; ++j) { + if (String.fromCharCode(i) == kPathSeparators[j]) { + disallow = true; + } + } + if (!disallow) { + name += String.fromCharCode(i); + } + } + // Add in CR, LF, FF, Tab, Vertical Tab + for (let i = 9; i < 14; i++) { + name += String.fromCharCode(i); + } + const handle = await dir.getFileHandle(name, {create: true}); + t.add_cleanup(() => dir.removeEntry(name)); + + assert_equals(handle.kind, 'file'); + assert_equals(handle.name, name); + assert_equals(await getFileSize(handle), 0); + assert_equals(await getFileContents(handle), ''); +}, 'getFileHandle(create=true) creates an empty file with all valid ASCII characters in the name'); + +directory_test(async (t, dir) => { + var name; + // A non-ASCII name + name = 'Funny cat \u{1F639}' + const handle = await dir.getFileHandle(name, {create: true}); + t.add_cleanup(() => dir.removeEntry(name)); + + assert_equals(handle.kind, 'file'); + assert_equals(handle.name, name); + assert_equals(await getFileSize(handle), 0); + assert_equals(await getFileContents(handle), ''); +}, 'getFileHandle(create=true) creates an empty file with non-ASCII characters in the name'); + +directory_test(async (t, dir) => { + const existing_handle = await createFileWithContents( + t, 'existing-file', '1234567890', /*parent=*/ dir); + const handle = await dir.getFileHandle('existing-file'); + + assert_equals(handle.kind, 'file'); + assert_equals(handle.name, 'existing-file'); + assert_equals(await getFileSize(handle), 10); + assert_equals(await getFileContents(handle), '1234567890'); +}, 'getFileHandle(create=false) returns existing files'); + +directory_test(async (t, dir) => { + const existing_handle = await createFileWithContents( + t, 'file-with-contents', '1234567890', /*parent=*/ dir); + const handle = await dir.getFileHandle('file-with-contents', {create: true}); + + assert_equals(handle.kind, 'file'); + assert_equals(handle.name, 'file-with-contents'); + assert_equals(await getFileSize(handle), 10); + assert_equals(await getFileContents(handle), '1234567890'); +}, 'getFileHandle(create=true) returns existing files without erasing'); + +directory_test(async (t, dir) => { + const dir_handle = await dir.getDirectoryHandle('dir-name', {create: true}); + t.add_cleanup(() => dir.removeEntry('dir-name', {recursive: true})); + + await promise_rejects_dom( + t, 'TypeMismatchError', dir.getFileHandle('dir-name')); +}, 'getFileHandle(create=false) when a directory already exists with the same name'); + +directory_test(async (t, dir) => { + const dir_handle = await dir.getDirectoryHandle('dir-name', {create: true}); + t.add_cleanup(() => dir.removeEntry('dir-name', {recursive: true})); + + await promise_rejects_dom( + t, 'TypeMismatchError', dir.getFileHandle('dir-name', {create: true})); +}, 'getFileHandle(create=true) when a directory already exists with the same name'); + +directory_test(async (t, dir) => { + await promise_rejects_js(t, TypeError, dir.getFileHandle('', {create: true})); + await promise_rejects_js( + t, TypeError, dir.getFileHandle('', {create: false})); +}, 'getFileHandle() with empty name'); + +directory_test(async (t, dir) => { + await promise_rejects_js(t, TypeError, dir.getFileHandle(kCurrentDirectory)); + await promise_rejects_js( + t, TypeError, dir.getFileHandle(kCurrentDirectory, {create: true})); +}, `getFileHandle() with "${kCurrentDirectory}" name`); + +directory_test(async (t, dir) => { + const subdir = await createDirectory(t, 'subdir-name', /*parent=*/ dir); + + await promise_rejects_js( + t, TypeError, subdir.getFileHandle(kParentDirectory)); + await promise_rejects_js( + t, TypeError, subdir.getFileHandle(kParentDirectory, {create: true})); +}, `getFileHandle() with "${kParentDirectory}" name`); + +directory_test(async (t, dir) => { + const subdir_name = 'subdir-name'; + const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir); + + const file_name = 'file-name'; + await createEmptyFile(t, file_name, /*parent=*/ subdir); + + for (let i = 0; i < kPathSeparators.length; ++i) { + const path_with_separator = + `${subdir_name}${kPathSeparators[i]}${file_name}`; + await promise_rejects_js( + t, TypeError, dir.getFileHandle(path_with_separator), + `getFileHandle() must reject names containing "${kPathSeparators[i]}"`); + } +}, 'getFileHandle(create=false) with a path separator when the file exists.'); + +directory_test(async (t, dir) => { + const subdir_name = 'subdir-name'; + const subdir = await createDirectory(t, subdir_name, /*parent=*/ dir); + + for (let i = 0; i < kPathSeparators.length; ++i) { + const path_with_separator = `${subdir_name}${kPathSeparators[i]}file_name`; + await promise_rejects_js( + t, TypeError, dir.getFileHandle(path_with_separator, {create: true}), + `getFileHandle(create=true) must reject names containing "${ + kPathSeparators[i]}"`); + } +}, 'getFileHandle(create=true) with a path separator'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js new file mode 100644 index 0000000000..815ae21936 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-iteration.js @@ -0,0 +1,100 @@ +'use strict'; + +directory_test(async (t, root) => { + const file_name1 = 'foo1.txt'; + const file_name2 = 'foo2.txt'; + await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root); + await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root); + + for await (let entry of root) { + break; + } + +}, 'returning early from an iteration doesn\'t crash'); + +directory_test(async (t, root) => { + const file_name1 = 'foo1.txt'; + const file_name2 = 'foo2.txt'; + await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root); + await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root); + + let names = []; + for await (let entry of root) { + assert_true(Array.isArray(entry)); + assert_equals(entry.length, 2); + assert_equals(typeof entry[0], 'string'); + assert_true(entry[1] instanceof FileSystemFileHandle); + assert_equals(entry[0], entry[1].name); + names.push(entry[0]); + } + names.sort(); + assert_array_equals(names, [file_name1, file_name2]); + +}, '@@asyncIterator: full iteration works'); + +directory_test(async (t, root) => { + const file_name1 = 'foo1.txt'; + const file_name2 = 'foo2.txt'; + await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root); + await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root); + + let names = []; + for await (let entry of root.entries()) { + assert_true(Array.isArray(entry)); + assert_equals(entry.length, 2); + assert_equals(typeof entry[0], 'string'); + assert_true(entry[1] instanceof FileSystemFileHandle); + assert_equals(entry[0], entry[1].name); + names.push(entry[0]); + } + names.sort(); + assert_array_equals(names, [file_name1, file_name2]); +}, 'entries: full iteration works'); + +directory_test(async (t, root) => { + const file_name1 = 'foo1.txt'; + const file_name2 = 'foo2.txt'; + await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root); + await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root); + + let names = []; + for await (let entry of root.values()) { + assert_true(entry instanceof FileSystemFileHandle); + names.push(entry.name); + } + names.sort(); + assert_array_equals(names, [file_name1, file_name2]); +}, 'values: full iteration works'); + +directory_test(async (t, root) => { + const file_name1 = 'foo1.txt'; + const file_name2 = 'foo2.txt'; + await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root); + await createFileWithContents(t, file_name2, 'contents', /*parent=*/ root); + + let names = []; + for await (let entry of root.keys()) { + assert_equals(typeof entry, 'string'); + names.push(entry); + } + names.sort(); + assert_array_equals(names, [file_name1, file_name2]); +}, 'keys: full iteration works'); + +directory_test(async (t, root) => { + const file_name1 = 'foo1.txt'; + await createFileWithContents(t, file_name1, 'contents', /*parent=*/ root); + + const next = (() => { + const iterator = root.entries(); + return iterator.next(); + })(); + garbageCollect(); + let entry = await next; + assert_false(entry.done); + assert_true(Array.isArray(entry.value)); + assert_equals(entry.value.length, 2); + assert_equals(entry.value[0], file_name1); + assert_true(entry.value[1] instanceof FileSystemFileHandle); + assert_equals(entry.value[1].name, file_name1); +}, 'iteration while iterator gets garbage collected'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js new file mode 100644 index 0000000000..07d26e9011 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-removeEntry.js @@ -0,0 +1,222 @@ +'use strict'; + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await root.removeEntry('file-to-remove'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); + await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle)); +}, 'removeEntry() to remove a file'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await root.removeEntry('file-to-remove'); + + await promise_rejects_dom( + t, 'NotFoundError', root.removeEntry('file-to-remove')); +}, 'removeEntry() on an already removed file should fail'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await root.removeEntry('dir-to-remove'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); +}, 'removeEntry() to remove an empty directory'); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir-to-remove', root); + await createFileWithContents(t, 'file-in-dir', 'abc', dir); + + await promise_rejects_dom( + t, 'InvalidModificationError', root.removeEntry('dir-to-remove')); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-to-remove/']); + assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']); +}, 'removeEntry() on a non-empty directory should fail'); + +directory_test(async (t, root) => { + // root + // ├──file-to-keep + // ├──dir-to-remove + // ├── file0 + // ├── dir1-in-dir + // │  └── file1 + // └── dir2 + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await createEmptyFile(t, 'file0', dir); + const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir); + await createEmptyFile(t, 'file1', dir1_in_dir); + await createDirectory(t, 'dir2-in-dir', dir); + + await root.removeEntry('dir-to-remove', {recursive: true}); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); +}, 'removeEntry() on a directory recursively should delete all sub-items'); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir', root); + await promise_rejects_js(t, TypeError, dir.removeEntry('')); +}, 'removeEntry() with empty name should fail'); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir', root); + await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory)); +}, `removeEntry() with "${kCurrentDirectory}" name should fail`); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir', root); + await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory)); +}, `removeEntry() with "${kParentDirectory}" name should fail`); + +directory_test(async (t, root) => { + const dir_name = 'dir-name'; + const dir = await createDirectory(t, dir_name, root); + + const file_name = 'file-name'; + await createEmptyFile(t, file_name, dir); + + for (let i = 0; i < kPathSeparators.length; ++i) { + const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`; + await promise_rejects_js( + t, TypeError, root.removeEntry(path_with_separator), + `removeEntry() must reject names containing "${kPathSeparators[i]}"`); + } +}, 'removeEntry() with a path separator should fail.'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await root.removeEntry('file-to-remove'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); + await promise_rejects_dom(t, 'NotFoundError', getFileContents(handle)); +}, 'removeEntry() to remove a file'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await root.removeEntry('file-to-remove'); + + await promise_rejects_dom(t, 'NotFoundError', root.removeEntry('file-to-remove')); +}, 'removeEntry() on an already removed file should fail'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await root.removeEntry('dir-to-remove'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); +}, 'removeEntry() to remove an empty directory'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + t.add_cleanup(() => root.removeEntry('dir-to-remove', {recursive: true})); + await createEmptyFile(t, 'file-in-dir', dir); + + await promise_rejects_dom( + t, 'InvalidModificationError', root.removeEntry('dir-to-remove')); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-to-remove/']); + assert_array_equals(await getSortedDirectoryEntries(dir), ['file-in-dir']); +}, 'removeEntry() on a non-empty directory should fail'); + +directory_test(async (t, root) => { + // root + // ├──file-to-keep + // ├──dir-to-remove + // ├── file0 + // ├── dir1-in-dir + // │  └── file1 + // └── dir2 + const dir = await root.getDirectoryHandle('dir-to-remove', {create: true}); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + await createEmptyFile(t, 'file0', dir); + const dir1_in_dir = await createDirectory(t, 'dir1-in-dir', dir); + await createEmptyFile(t, 'file1', dir1_in_dir); + await createDirectory(t, 'dir2-in-dir', dir); + + await root.removeEntry('dir-to-remove', {recursive: true}); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); +}, 'removeEntry() on a directory recursively should delete all sub-items'); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir', root); + await promise_rejects_js(t, TypeError, dir.removeEntry('')); +}, 'removeEntry() with empty name should fail'); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir', root); + await promise_rejects_js(t, TypeError, dir.removeEntry(kCurrentDirectory)); +}, `removeEntry() with "${kCurrentDirectory}" name should fail`); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'dir', root); + await promise_rejects_js(t, TypeError, dir.removeEntry(kParentDirectory)); +}, `removeEntry() with "${kParentDirectory}" name should fail`); + +directory_test(async (t, root) => { + const dir_name = 'dir-name'; + const dir = await createDirectory(t, dir_name, root); + + const file_name = 'file-name'; + await createEmptyFile(t, file_name, dir); + + for (let i = 0; i < kPathSeparators.length; ++i) { + const path_with_separator = `${dir_name}${kPathSeparators[i]}${file_name}`; + await promise_rejects_js( + t, TypeError, root.removeEntry(path_with_separator), + `removeEntry() must reject names containing "${kPathSeparators[i]}"`); + } +}, 'removeEntry() with a path separator should fail.'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await createFileWithContents(t, 'file-to-keep', 'abc', root); + + const writable = await cleanup_writable(t, await handle.createWritable()); + await promise_rejects_dom( + t, 'NoModificationAllowedError', root.removeEntry('file-to-remove')); + + await writable.close(); + await root.removeEntry('file-to-remove'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-to-keep']); +}, 'removeEntry() while the file has an open writable fails'); + +directory_test(async (t, root) => { + const dir_name = 'dir-name'; + const dir = await createDirectory(t, dir_name, root); + + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', dir); + await createFileWithContents(t, 'file-to-keep', 'abc', dir); + + const writable = await cleanup_writable(t, await handle.createWritable()); + await promise_rejects_dom( + t, 'NoModificationAllowedError', root.removeEntry(dir_name)); + + await writable.close(); + assert_array_equals( + await getSortedDirectoryEntries(dir), ['file-to-keep', 'file-to-remove']); + + await dir.removeEntry('file-to-remove'); + assert_array_equals(await getSortedDirectoryEntries(dir), ['file-to-keep']); +}, 'removeEntry() of a directory while a containing file has an open writable fails'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-remove', '12345', root); + await root.removeEntry('file-to-remove'); + + await promise_rejects_dom(t, 'NotFoundError', cleanup_writable(t, handle.createWritable({keepExistingData: true}))); + + assert_array_equals( + await getSortedDirectoryEntries(root), + []); +}, 'createWritable after removeEntry succeeds but doesnt recreate the file'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js new file mode 100644 index 0000000000..a8900f97e5 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemDirectoryHandle-resolve.js @@ -0,0 +1,27 @@ +'use strict'; + +directory_test(async (t, root_dir) => { + assert_array_equals(await root_dir.resolve(root_dir), []); +}, 'Resolve returns empty array for same directory'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + const file = await createEmptyFile(t, 'file-name', subdir); + + assert_array_equals(await root_dir.resolve(file), ['subdir-name', 'file-name']); +}, 'Resolve returns correct path'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir😊', root_dir); + const file = await createEmptyFile(t, 'file-name', subdir); + + assert_array_equals(await root_dir.resolve(file), ['subdir😊', 'file-name']); + assert_array_equals(await root_dir.resolve(subdir), ['subdir😊']); +}, 'Resolve returns correct path with non-ascii characters'); + +directory_test(async (t, root_dir) => { + const subdir = await createDirectory(t, 'subdir-name', root_dir); + const file = await createEmptyFile(t, 'file-name', root_dir); + + assert_equals(await subdir.resolve(file), null); +}, 'Resolve returns null when entry is not a child'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js new file mode 100644 index 0000000000..302d4a5a7a --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-create-sync-access-handle.js @@ -0,0 +1,26 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js +// /fs/resources/test-helpers.js + +directory_test(async (t, root_dir) => { + assert_true( + file_system_type == 'sandboxed' || file_system_type == 'local', + 'File system type should be sandboxed or local.'); + const expect_success = file_system_type == 'sandboxed'; + + const dedicated_worker = + create_dedicated_worker(t, kDedicatedWorkerMessageTarget); + const file_handle = + await root_dir.getFileHandle('sync-access-handle-file', {create: true}); + + dedicated_worker.postMessage( + {type: 'create-sync-access-handle', file_handle}); + + const event_watcher = new EventWatcher(t, dedicated_worker, 'message'); + const message_event = await event_watcher.wait_for('message'); + const response = message_event.data; + + assert_equals(response.success, expect_success); +}, 'Attempt to create a sync access handle.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js new file mode 100644 index 0000000000..b7ceb5ee7a --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-getFile.js @@ -0,0 +1,42 @@ +'use strict'; + +directory_test(async (t, root) => { + const fileContents = 'awesome content'; + let handle = await createFileWithContents(t, 'foo.txt', fileContents, /*parent=*/ root); + let file = await handle.getFile(); + let slice = file.slice(1, file.size); + let actualContents = await slice.text(); + assert_equals(actualContents, fileContents.slice(1, fileContents.length)); +}, 'getFile() provides a file that can be sliced'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'mtime.txt', root); + let file = await handle.getFile(); + const first_mtime = file.lastModified; + + // We wait for 2s here to ensure that the files do not have the + // same modification time. Some filesystems have low resolutions + // for modification timestamps. + let timeout = new Promise(resolve => { + t.step_timeout(resolve, 2000); + }); + await timeout; + + const writer = await cleanup_writable(t, await handle.createWritable({keepExistingData: false})); + await writer.write(new Blob(['foo'])); + await writer.close(); + + file = await handle.getFile(); + const second_mtime = file.lastModified; + + // We wait for 5 ms here to ensure that `lastModified` + // from the File objects is stable between getFile invocations. + timeout = new Promise(resolve => { + t.step_timeout(resolve, 5); + }); + await timeout; + let fileReplica = await handle.getFile(); + assert_equals(second_mtime, fileReplica.lastModified); + + assert_less_than(first_mtime, second_mtime); +}, 'getFile() returns last modified time'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js new file mode 100644 index 0000000000..dd848626e4 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemFileHandle-move.js @@ -0,0 +1,369 @@ +// META: script=resources/test-helpers.js + +'use strict'; + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await handle.move('file-after'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(name) to rename a file'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await handle.move('file-after'); + const newhandle = await root.getFileHandle('file-after'); + assert_equals(await getFileContents(newhandle), 'foo'); + assert_equals(await getFileSize(newhandle), 3); +}, 'get a handle to a moved file'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await handle.move('file-before'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(name) to rename a file the same name'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await promise_rejects_js(t, TypeError, handle.move('')); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move("") to rename a file fails'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-1', 'foo', root); + + await handle.move('file-2'); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-2']); + + await handle.move('file-3'); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-3']); + + await handle.move('file-1'); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-1']); +}, 'move(name) can be called multiple times'); + +directory_test(async (t, root) => { + const dir = await root.getDirectoryHandle('dir', {create: true}); + const handle = await createFileWithContents(t, 'file-before', 'foo', dir); + await promise_rejects_js(t, TypeError, handle.move('Lorem.')); + + assert_array_equals(await getSortedDirectoryEntries(root), ['dir/']); + assert_array_equals(await getSortedDirectoryEntries(dir), ['file-before']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(name) with a name with a trailing period should fail'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await promise_rejects_js(t, TypeError, handle.move('test/test')); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(name) with a name with invalid characters should fail'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'abc', root); + + // Cannot rename handle with an active writable. + const stream = await cleanup_writable(t, await handle.createWritable()); + await promise_rejects_dom( + t, 'NoModificationAllowedError', handle.move('file-after')); + + // Can move handle once the writable is closed. + await stream.close(); + await handle.move('file-after'); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']); +}, 'move(name) while the file has an open writable fails'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'abc', root); + const handle_dest = + await createFileWithContents(t, 'file-after', '123', root); + + // Cannot overwrite a handle with an active writable. + const stream = await cleanup_writable(t, await handle_dest.createWritable()); + await promise_rejects_dom( + t, 'NoModificationAllowedError', handle.move('file-after')); + + await stream.close(); + assert_array_equals( + await getSortedDirectoryEntries(root), ['file-after', 'file-before']); +}, 'move(name) while the destination file has an open writable fails'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'abc', root); + const handle_dest = + await createFileWithContents(t, 'file-after', '123', root); + + await handle.move('file-after'); + assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']); + assert_equals(await getFileContents(handle), 'abc'); + assert_equals(await getFileContents(handle_dest), 'abc'); +}, 'move(name) can overwrite an existing file'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await handle.move(root, 'file-after'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-after']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(dir, name) to rename a file'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await handle.move(root, 'file-before'); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(dir, name) to rename a file the same name'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file', 'abc', dir_src); + await file.move(dir_dest); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + assert_array_equals(await getSortedDirectoryEntries(dir_src), []); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']); + assert_equals(await getFileContents(file), 'abc'); + assert_equals(await getFileSize(file), 3); +}, 'move(dir) to move a file to a new directory'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file', 'abc', dir_src); + await promise_rejects_js(t, TypeError, file.move(dir_dest, '')); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + assert_array_equals(await getSortedDirectoryEntries(dir_src), ['file']); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), []); + assert_equals(await getFileContents(file), 'abc'); + assert_equals(await getFileSize(file), 3); +}, 'move(dir, "") to move a file to a new directory fails'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = + await createFileWithContents(t, 'file-in-dir-src', 'abc', dir_src); + await file.move(dir_dest, 'file-in-dir-dest'); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + assert_array_equals(await getSortedDirectoryEntries(dir_src), []); + assert_array_equals( + await getSortedDirectoryEntries(dir_dest), ['file-in-dir-dest']); + assert_equals(await getFileContents(file), 'abc'); + assert_equals(await getFileSize(file), 3); +}, 'move(dir, name) to move a file to a new directory'); + +directory_test(async (t, root) => { + const dir1 = await root.getDirectoryHandle('dir1', {create: true}); + const dir2 = await root.getDirectoryHandle('dir2', {create: true}); + const handle = await createFileWithContents(t, 'file', 'foo', root); + + await handle.move(dir1); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); + assert_array_equals(await getSortedDirectoryEntries(dir1), ['file']); + assert_array_equals(await getSortedDirectoryEntries(dir2), []); + assert_equals(await getFileContents(handle), 'foo'); + + await handle.move(dir2); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); + assert_array_equals(await getSortedDirectoryEntries(dir1), []); + assert_array_equals(await getSortedDirectoryEntries(dir2), ['file']); + assert_equals(await getFileContents(handle), 'foo'); + + await handle.move(root); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file']); + assert_array_equals(await getSortedDirectoryEntries(dir1), []); + assert_array_equals(await getSortedDirectoryEntries(dir2), []); + assert_equals(await getFileContents(handle), 'foo'); +}, 'move(dir) can be called multiple times'); + +directory_test(async (t, root) => { + const dir1 = await root.getDirectoryHandle('dir1', {create: true}); + const dir2 = await root.getDirectoryHandle('dir2', {create: true}); + const handle = await createFileWithContents(t, 'file', 'foo', root); + + await handle.move(dir1, 'file-1'); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); + assert_array_equals(await getSortedDirectoryEntries(dir1), ['file-1']); + assert_array_equals(await getSortedDirectoryEntries(dir2), []); + assert_equals(await getFileContents(handle), 'foo'); + + await handle.move(dir2, 'file-2'); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir1/', 'dir2/']); + assert_array_equals(await getSortedDirectoryEntries(dir1), []); + assert_array_equals(await getSortedDirectoryEntries(dir2), ['file-2']); + assert_equals(await getFileContents(handle), 'foo'); + + await handle.move(root, 'file-3'); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir1/', 'dir2/', 'file-3']); + assert_array_equals(await getSortedDirectoryEntries(dir1), []); + assert_array_equals(await getSortedDirectoryEntries(dir2), []); + assert_equals(await getFileContents(handle), 'foo'); +}, 'move(dir, name) can be called multiple times'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents(t, 'file-before', 'foo', root); + await promise_rejects_js(t, TypeError, handle.move(root, '..')); + + assert_array_equals(await getSortedDirectoryEntries(root), ['file-before']); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'move(dir, name) with a name with invalid characters should fail'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file', 'abc', dir_src); + + // Cannot move handle with an active writable. + const stream = await cleanup_writable(t, await file.createWritable()); + await promise_rejects_dom(t, 'NoModificationAllowedError', file.move(dir_dest)); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + // Assert the file hasn't been moved to the destination directory. + assert_array_equals(await getSortedDirectoryEntries(dir_dest), []); + + // Can move handle once the writable is closed. + await stream.close(); + await file.move(dir_dest); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + assert_array_equals(await getSortedDirectoryEntries(dir_src), []); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']); +}, 'move(dir) while the file has an open writable fails'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file-before', 'abc', dir_src); + + // Cannot move handle with an active writable. + const stream = await cleanup_writable(t, await file.createWritable()); + await promise_rejects_dom(t, 'NoModificationAllowedError', file.move(dir_dest)); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + // Assert the file hasn't been moved to the destination directory. + assert_array_equals(await getSortedDirectoryEntries(dir_dest), []); + + // Can move handle once the writable is closed. + await stream.close(); + await file.move(dir_dest, 'file-after'); + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + assert_array_equals(await getSortedDirectoryEntries(dir_src), []); + assert_array_equals( + await getSortedDirectoryEntries(dir_dest), ['file-after']); +}, 'move(dir, name) while the file has an open writable fails'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file', 'abc', dir_src); + const file_dest = await createFileWithContents(t, 'file', '123', dir_dest); + + // Cannot overwrite handle with an active writable. + const stream = await cleanup_writable(t, await file_dest.createWritable()); + await promise_rejects_dom(t, 'NoModificationAllowedError', file.move(dir_dest)); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + // Assert the file is still in the source directory. + assert_array_equals(await getSortedDirectoryEntries(dir_src), ['file']); + + await stream.close(); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']); +}, 'move(dir) while the destination file has an open writable fails'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file', 'abc', dir_src); + const file_dest = await createFileWithContents(t, 'file', '123', dir_dest); + + await file.move(dir_dest); + assert_array_equals(await getSortedDirectoryEntries(dir_src), []); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file']); + assert_equals(await getFileContents(file), 'abc'); + assert_equals(await getFileContents(file_dest), 'abc'); +}, 'move(dir) can overwrite an existing file'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file-src', 'abc', dir_src); + const file_dest = + await createFileWithContents(t, 'file-dest', '123', dir_dest); + + // Cannot overwrite handle with an active writable. + const stream = await cleanup_writable(t, await file_dest.createWritable()); + await promise_rejects_dom( + t, 'NoModificationAllowedError', file.move(dir_dest, 'file-dest')); + + assert_array_equals( + await getSortedDirectoryEntries(root), ['dir-dest/', 'dir-src/']); + // Assert the file is still in the source directory. + assert_array_equals(await getSortedDirectoryEntries(dir_src), ['file-src']); + + await stream.close(); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file-dest']); +}, 'move(dir, name) while the destination file has an open writable fails'); + +directory_test(async (t, root) => { + const dir_src = await root.getDirectoryHandle('dir-src', {create: true}); + const dir_dest = await root.getDirectoryHandle('dir-dest', {create: true}); + const file = await createFileWithContents(t, 'file-src', 'abc', dir_src); + const file_dest = + await createFileWithContents(t, 'file-dest', '123', dir_dest); + + await file.move(dir_dest, 'file-dest'); + + // Assert the file has been moved to the destination directory and renamed. + assert_array_equals(await getSortedDirectoryEntries(dir_src), []); + assert_array_equals(await getSortedDirectoryEntries(dir_dest), ['file-dest']); + assert_equals(await getFileContents(file), 'abc'); + assert_equals(await getFileContents(file_dest), 'abc'); +}, 'move(dir, name) can overwrite an existing file'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'file-to-move', '12345', root); + const handle2 = handle; + + await handle.move('file-was-moved'); + + assert_equals(await getFileContents(handle), '12345'); + assert_equals(await getFileSize(handle), 5); + assert_equals(await getFileContents(handle2), '12345'); + assert_equals(await getFileSize(handle2), 5); + + assert_array_equals( + await getSortedDirectoryEntries(root), + ['file-was-moved']); +}, 'FileSystemFileHandles are references, not paths'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js b/testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js new file mode 100644 index 0000000000..580da69cc9 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemSyncAccessHandle-flush.js @@ -0,0 +1,8 @@ +'use strict'; + +// This script depends on the following scripts: +// /fs/resources/messaging-helpers.js + +sync_access_handle_test(async handle => { + await handle.flush(); +}, 'Test flush on an empty file.'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js new file mode 100644 index 0000000000..79e4cfb4f2 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-piped.js @@ -0,0 +1,137 @@ +'use strict'; + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'foo_string.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const rs = recordingReadableStream({ + start(controller) { + controller.enqueue('foo_string'); + controller.close(); + } + }); + + await rs.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'foo_string'); + assert_equals(await getFileSize(handle), 10); +}, 'can be piped to with a string'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'foo_arraybuf.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + const buf = new ArrayBuffer(3); + const intView = new Uint8Array(buf); + intView[0] = 0x66; + intView[1] = 0x6f; + intView[2] = 0x6f; + + const rs = recordingReadableStream({ + start(controller) { + controller.enqueue(buf); + controller.close(); + } + }); + + await rs.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'can be piped to with an ArrayBuffer'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'foo_blob.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const rs = recordingReadableStream({ + start(controller) { + controller.enqueue(new Blob(['foo'])); + controller.close(); + } + }); + + await rs.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'can be piped to with a Blob'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'foo_write_param.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const rs = recordingReadableStream({ + start(controller) { + controller.enqueue({type: 'write', data: 'foobar'}); + controller.close(); + } + }); + + await rs.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'foobar'); + assert_equals(await getFileSize(handle), 6); +}, 'can be piped to with a param object with write command'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'foo_write_param.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const rs = recordingReadableStream({ + start(controller) { + controller.enqueue({type: 'write', data: 'foobar'}); + controller.enqueue({type: 'truncate', size: 10}); + controller.enqueue({type: 'write', position: 0, data: 'baz'}); + controller.close(); + } + }); + + await rs.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'bazbar\0\0\0\0'); + assert_equals(await getFileSize(handle), 10); +}, 'can be piped to with a param object with multiple commands'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'foo_write_queued.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const rs = recordingReadableStream({ + start(controller) { + controller.enqueue('foo'); + controller.enqueue('bar'); + controller.enqueue('baz'); + controller.close(); + } + }); + + await rs.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'foobarbaz'); + assert_equals(await getFileSize(handle), 9); +}, 'multiple operations can be queued'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'fetched.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const response = await fetch('data:text/plain,fetched from far'); + const body = await response.body; + await body.pipeTo(wfs, { preventCancel: true }); + assert_equals(await getFileContents(handle), 'fetched from far'); + assert_equals(await getFileSize(handle), 16); +}, 'plays well with fetch'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'aborted should_be_empty.txt', root); + const wfs = await cleanup_writable(t, await handle.createWritable()); + + const response = await fetch('data:text/plain,fetched from far'); + const body = await response.body; + + const abortController = new AbortController(); + const signal = abortController.signal; + + const promise = body.pipeTo(wfs, { signal }); + await abortController.abort(); + + await promise_rejects_dom(t, 'AbortError', promise, 'stream is aborted'); + await promise_rejects_js(t, TypeError, wfs.close(), 'stream cannot be closed to flush writes'); + + assert_equals(await getFileContents(handle), ''); + assert_equals(await getFileSize(handle), 0); +}, 'abort() aborts write'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js new file mode 100644 index 0000000000..66d6a944a5 --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream-write.js @@ -0,0 +1,334 @@ +'use strict'; + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'empty_blob', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write(new Blob([])); + await stream.close(); + + assert_equals(await getFileContents(handle), ''); + assert_equals(await getFileSize(handle), 0); +}, 'write() with an empty blob to an empty file'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'valid_blob', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write(new Blob(['1234567890'])); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() a blob to an empty file'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'write_param_empty', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write({type: 'write', data: '1234567890'}); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() with WriteParams without position to an empty file'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'string_zero_offset', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write({type: 'write', position: 0, data: '1234567890'}); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() a string to an empty file with zero offset'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'blob_zero_offset', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write({type: 'write', position: 0, data: new Blob(['1234567890'])}); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() a blob to an empty file with zero offset'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'write_appends', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('12345'); + await stream.write('67890'); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() called consecutively appends'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'write_appends_object_string', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('12345'); + await stream.write({type: 'write', data: '67890'}); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() WriteParams without position and string appends'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'write_appends_object_blob', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('12345'); + await stream.write({type: 'write', data: new Blob(['67890'])}); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234567890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() WriteParams without position and blob appends'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'string_with_offset', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('1234567890'); + await stream.write({type: 'write', position: 4, data: 'abc'}); + await stream.close(); + + assert_equals(await getFileContents(handle), '1234abc890'); + assert_equals(await getFileSize(handle), 10); +}, 'write() called with a string and a valid offset'); + +directory_test(async (t, root) => { +const handle = await createEmptyFile(t, 'blob_with_offset', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + +await stream.write('1234567890'); +await stream.write({type: 'write', position: 4, data: new Blob(['abc'])}); +await stream.close(); + +assert_equals(await getFileContents(handle), '1234abc890'); +assert_equals(await getFileSize(handle), 10); +}, 'write() called with a blob and a valid offset'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'bad_offset', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write({type: 'write', position: 4, data: new Blob(['abc'])}); + await stream.close(); + + assert_equals(await getFileContents(handle), '\0\0\0\0abc'); + assert_equals(await getFileSize(handle), 7); +}, 'write() called with an offset beyond the end of the file'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'empty_string', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write(''); + await stream.close(); + assert_equals(await getFileContents(handle), ''); + assert_equals(await getFileSize(handle), 0); +}, 'write() with an empty string to an empty file'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'valid_utf8_string', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('foo🤘'); + await stream.close(); + assert_equals(await getFileContents(handle), 'foo🤘'); + assert_equals(await getFileSize(handle), 7); +}, 'write() with a valid utf-8 string'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'string_with_unix_line_ending', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('foo\n'); + await stream.close(); + assert_equals(await getFileContents(handle), 'foo\n'); + assert_equals(await getFileSize(handle), 4); +}, 'write() with a string with unix line ending preserved'); + +directory_test(async (t, root) => { + const handle = + await createEmptyFile(t, 'string_with_windows_line_ending', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('foo\r\n'); + await stream.close(); + assert_equals(await getFileContents(handle), 'foo\r\n'); + assert_equals(await getFileSize(handle), 5); +}, 'write() with a string with windows line ending preserved'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'empty_array_buffer', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + const buf = new ArrayBuffer(0); + await stream.write(buf); + await stream.close(); + assert_equals(await getFileContents(handle), ''); + assert_equals(await getFileSize(handle), 0); +}, 'write() with an empty array buffer to an empty file'); + +directory_test(async (t, root) => { + const handle = + await createEmptyFile(t, 'valid_string_typed_byte_array', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + const buf = new ArrayBuffer(3); + const intView = new Uint8Array(buf); + intView[0] = 0x66; + intView[1] = 0x6f; + intView[2] = 0x6f; + await stream.write(buf); + await stream.close(); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); +}, 'write() with a valid typed array buffer'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'atomic_writes.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + await stream.write('foox'); + + const stream2 = await cleanup_writable(t, await handle.createWritable()); + await stream2.write('bar'); + + assert_equals(await getFileSize(handle), 0); + + await stream2.close(); + assert_equals(await getFileContents(handle), 'bar'); + assert_equals(await getFileSize(handle), 3); + + await stream.close(); + assert_equals(await getFileContents(handle), 'foox'); + assert_equals(await getFileSize(handle), 4); +}, 'atomic writes: writable file streams make atomic changes on close'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'atomic_write_after_close.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + await stream.write('foo'); + + await stream.close(); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); + + await promise_rejects_js( + t, TypeError, stream.write('abc')); +}, 'atomic writes: write() after close() fails'); + +directory_test(async (t, root) => { + const handle = + await createEmptyFile(t, 'atomic_truncate_after_close.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + await stream.write('foo'); + + await stream.close(); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); + + await promise_rejects_js(t, TypeError, stream.truncate(0)); +}, 'atomic writes: truncate() after close() fails'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'atomic_close_after_close.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + await stream.write('foo'); + + await stream.close(); + assert_equals(await getFileContents(handle), 'foo'); + assert_equals(await getFileSize(handle), 3); + + await promise_rejects_js(t, TypeError, stream.close()); +}, 'atomic writes: close() after close() fails'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'there_can_be_only_one.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + await stream.write('foo'); + + // This test might be flaky if there is a race condition allowing + // close() to be called multiple times. + const success_promises = + [...Array(100)].map(() => stream.close().then(() => 1).catch(() => 0)); + const close_attempts = await Promise.all(success_promises); + const success_count = close_attempts.reduce((x, y) => x + y); + assert_equals(success_count, 1); +}, 'atomic writes: only one close() operation may succeed'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'writer_written', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + assert_false(stream.locked); + const writer = stream.getWriter(); + assert_true(stream.locked); + + await writer.write('foo'); + await writer.write(new Blob(['bar'])); + await writer.write({type: 'seek', position: 0}); + await writer.write({type: 'write', data: 'baz'}); + await writer.close(); + + assert_equals(await getFileContents(handle), 'bazbar'); + assert_equals(await getFileSize(handle), 6); +}, 'getWriter() can be used'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents( + t, 'content.txt', 'very long string', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await promise_rejects_dom( + t, 'SyntaxError', stream.write({type: 'truncate'}), + 'truncate without size'); +}, 'WriteParams: truncate missing size param'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'content.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await promise_rejects_dom( + t, 'SyntaxError', stream.write({type: 'write'}), 'write without data'); +}, 'WriteParams: write missing data param'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'content.txt', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await promise_rejects_js( + t, TypeError, stream.write({type: 'write', data: null}), + 'write with null data'); +}, 'WriteParams: write null data param'); + +directory_test(async (t, root) => { + const handle = + await createFileWithContents(t, 'content.txt', 'seekable', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await promise_rejects_dom( + t, 'SyntaxError', stream.write({type: 'seek'}), 'seek without position'); +}, 'WriteParams: seek missing position param'); + +directory_test(async (t, root) => { + const source_file = + await createFileWithContents(t, 'source_file', 'source data', root); + const source_blob = await source_file.getFile(); + await root.removeEntry(source_file.name); + + const handle = await createEmptyFile(t, 'invalid_blob_test', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + await promise_rejects_dom(t, "NotFoundError", stream.write(source_blob)); + await promise_rejects_js(t, TypeError, stream.close()); + + assert_equals(await getFileContents(handle), ''); + assert_equals(await getFileSize(handle), 0); +}, 'write() with an invalid blob to an empty file should reject'); diff --git a/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js new file mode 100644 index 0000000000..19dc6371cf --- /dev/null +++ b/testing/web-platform/tests/fs/script-tests/FileSystemWritableFileStream.js @@ -0,0 +1,105 @@ +'use strict'; + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'trunc_shrink', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('1234567890'); + await stream.truncate(5); + await stream.close(); + + assert_equals(await getFileContents(handle), '12345'); + assert_equals(await getFileSize(handle), 5); +}, 'truncate() to shrink a file'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'trunc_grow', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + + await stream.write('abc'); + await stream.truncate(5); + await stream.close(); + + assert_equals(await getFileContents(handle), 'abc\0\0'); + assert_equals(await getFileSize(handle), 5); +}, 'truncate() to grow a file'); + +directory_test(async (t, root) => { + const dir = await createDirectory(t, 'parent_dir', root); + const file_name = 'create_writable_fails_when_dir_removed.txt'; + const handle = await createEmptyFile(t, file_name, dir); + + await root.removeEntry('parent_dir', {recursive: true}); + await promise_rejects_dom(t, 'NotFoundError', cleanup_writable(t, handle.createWritable())); +}, 'createWritable() fails when parent directory is removed'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents( + t, 'atomic_file_is_copied.txt', 'fooks', root); + const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: true})); + + await stream.write('bar'); + await stream.close(); + assert_equals(await getFileContents(handle), 'barks'); + assert_equals(await getFileSize(handle), 5); +}, 'createWritable({keepExistingData: true}): atomic writable file stream initialized with source contents'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents( + t, 'atomic_file_is_not_copied.txt', 'very long string', root); + const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: false})); + + await stream.write('bar'); + assert_equals(await getFileContents(handle), 'very long string'); + await stream.close(); + assert_equals(await getFileContents(handle), 'bar'); + assert_equals(await getFileSize(handle), 3); +}, 'createWritable({keepExistingData: false}): atomic writable file stream initialized with empty file'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents( + t, 'trunc_smaller_offset.txt', '1234567890', root); + const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: true})); + + await stream.truncate(5); + await stream.write('abc'); + await stream.close(); + + assert_equals(await getFileContents(handle), 'abc45'); + assert_equals(await getFileSize(handle), 5); +}, 'cursor position: truncate size > offset'); + +directory_test(async (t, root) => { + const handle = await createFileWithContents( + t, 'trunc_bigger_offset.txt', '1234567890', root); + const stream = await cleanup_writable(t, await handle.createWritable({keepExistingData: true})); + + await stream.seek(6); + await stream.truncate(5); + await stream.write('abc'); + await stream.close(); + + assert_equals(await getFileContents(handle), '12345abc'); + assert_equals(await getFileSize(handle), 8); +}, 'cursor position: truncate size < offset'); + +directory_test(async (t, root) => { + const handle = await createEmptyFile(t, 'contents', root); + const stream = await cleanup_writable(t, await handle.createWritable()); + assert_false(stream.locked); + + stream.write('abc'); + assert_false(stream.locked); + stream.write('def'); + assert_false(stream.locked); + stream.truncate(9); + assert_false(stream.locked); + stream.seek(0); + assert_false(stream.locked); + stream.write('xyz'); + assert_false(stream.locked); + await stream.close(); + + assert_equals(await getFileContents(handle), 'xyzdef\0\0\0'); + assert_equals(await getFileSize(handle), 9); +}, 'commands are queued, stream is unlocked after each operation'); |