diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/fs/test | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/fs/test')
72 files changed, 5260 insertions, 0 deletions
diff --git a/dom/fs/test/common/.eslintrc.js b/dom/fs/test/common/.eslintrc.js new file mode 100644 index 0000000000..d805cf6e0f --- /dev/null +++ b/dom/fs/test/common/.eslintrc.js @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +module.exports = { + globals: { + Assert: true, + exported_symbols: true, + require_module: true, + Utils: true, + }, +}; diff --git a/dom/fs/test/common/dummy.js b/dom/fs/test/common/dummy.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/fs/test/common/dummy.js diff --git a/dom/fs/test/common/mochitest.ini b/dom/fs/test/common/mochitest.ini new file mode 100644 index 0000000000..4926b81a48 --- /dev/null +++ b/dom/fs/test/common/mochitest.ini @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +support-files = + nsresult.js + test_basics.js + test_fileSystemDirectoryHandle.js + test_syncAccessHandle.js + test_writableFileStream.js + +[dummy.js] +skip-if = true diff --git a/dom/fs/test/common/moz.build b/dom/fs/test/common/moz.build new file mode 100644 index 0000000000..c8636196fc --- /dev/null +++ b/dom/fs/test/common/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += [ + "mochitest.ini", +] + +XPCSHELL_TESTS_MANIFESTS += [ + "xpcshell.ini", +] + +TESTING_JS_MODULES.dom.fs.test.common += [ + "nsresult.js", + "test_basics.js", + "test_fileSystemDirectoryHandle.js", + "test_syncAccessHandle.js", + "test_writableFileStream.js", +] diff --git a/dom/fs/test/common/nsresult.js b/dom/fs/test/common/nsresult.js new file mode 100644 index 0000000000..6e59b947a1 --- /dev/null +++ b/dom/fs/test/common/nsresult.js @@ -0,0 +1,9 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const nsresult = { + NS_ERROR_NOT_IMPLEMENTED: Cr.NS_ERROR_NOT_IMPLEMENTED, +}; +exported_symbols.nsresult = nsresult; diff --git a/dom/fs/test/common/test_basics.js b/dom/fs/test/common/test_basics.js new file mode 100644 index 0000000000..a040112356 --- /dev/null +++ b/dom/fs/test/common/test_basics.js @@ -0,0 +1,301 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +// This test must be first, since we need the actor not to be created already. +exported_symbols.testGetDirectoryTwice = async function () { + const promise1 = navigator.storage.getDirectory(); + const promise2 = navigator.storage.getDirectory(); + + await Promise.all([promise1, promise2]); + + Assert.ok(true, "Should not have thrown"); +}; + +exported_symbols.testGetDirectoryDoesNotThrow = async function () { + await navigator.storage.getDirectory(); + + Assert.ok(true, "Should not have thrown"); +}; + +exported_symbols.testGetDirectoryKindIsDirectory = async function () { + const root = await navigator.storage.getDirectory(); + + Assert.equal(root.kind, "directory"); +}; + +exported_symbols.testDirectoryHandleStringConversion = async function () { + const root = await navigator.storage.getDirectory(); + + Assert.equal( + "" + root, + "[object FileSystemDirectoryHandle]", + "Is directoryHandle convertible to string?" + ); +}; + +exported_symbols.testNewDirectoryHandleFromPrototype = async function () { + const root = await navigator.storage.getDirectory(); + + try { + Object.create(root.prototype); + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.ok(ex instanceof TypeError, "Threw the right error type"); + } +}; + +exported_symbols.testIsSameEntryRoot = async function () { + const root = await navigator.storage.getDirectory(); + try { + await root.move(root); + Assert.ok(false, "root should not be movable"); + } catch (ex) { + Assert.ok(true, "root isn't movable"); + } +}; + +exported_symbols.testDirectoryHandleSupportsKeysIterator = async function () { + const root = await navigator.storage.getDirectory(); + + const it = await root.keys(); + Assert.ok(!!it, "Does root support keys iterator?"); +}; + +exported_symbols.testKeysIteratorNextIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + + const it = await root.keys(); + Assert.ok(!!it, "Does root support keys iterator?"); + + const item = await it.next(); + Assert.ok(!!item, "Should return an item"); +}; + +exported_symbols.testDirectoryHandleSupportsValuesIterator = async function () { + const root = await navigator.storage.getDirectory(); + + const it = await root.values(); + Assert.ok(!!it, "Does root support values iterator?"); +}; + +exported_symbols.testValuesIteratorNextIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + + const it = await root.values(); + Assert.ok(!!it, "Does root support values iterator?"); + + const item = await it.next(); + Assert.ok(!!item, "Should return an item"); +}; + +exported_symbols.testDirectoryHandleSupportsEntriesIterator = + async function () { + const root = await navigator.storage.getDirectory(); + + const it = await root.entries(); + Assert.ok(!!it, "Does root support entries iterator?"); + }; + +exported_symbols.testEntriesIteratorNextIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + + const it = await root.entries(); + Assert.ok(!!it, "Does root support entries iterator?"); + + const item = await it.next(); + Assert.ok(!!item, "Should return an item"); +}; + +exported_symbols.testGetFileHandleIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + const allowCreate = { create: true }; + + const item = await root.getFileHandle("fileName", allowCreate); + Assert.ok(!!item, "Should return an item"); +}; + +exported_symbols.testGetDirectoryHandleIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + const allowCreate = { create: true }; + + const item = await root.getDirectoryHandle("dirName", allowCreate); + Assert.ok(!!item, "Should return an item"); +}; + +exported_symbols.testRemoveEntryIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + const removeOptions = { recursive: true }; + + await root.removeEntry("fileName", removeOptions); + await root.removeEntry("dirName", removeOptions); + try { + await root.removeEntry("doesNotExist", removeOptions); + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Should have thrown"); + Assert.equal( + ex.message, + "Entry not found", + "Threw the right error message" + ); + } +}; + +exported_symbols.testResolveIsCallable = async function () { + const root = await navigator.storage.getDirectory(); + const allowCreate = { create: true }; + const item = await root.getFileHandle("fileName", allowCreate); + + let path = await root.resolve(item); + Assert.equal(path.length, 1); + Assert.equal(path[0], "fileName", "Resolve got the right path"); +}; + +exported_symbols.testFileType = async function () { + const root = await navigator.storage.getDirectory(); + const allowCreate = { create: true }; + const nameStem = "testFileType"; + const empty = ""; + + const extensions = [ + "txt", + "jS", + "JSON", + "css", + "html", + "htm", + "xhtml", + "xml", + "xhtml+xml", + "png", + "apng", + "jPg", + "Jpeg", + "pdF", + "out", + "sh", + "ExE", + "psid", + "EXE ", + " EXE", + "EX\uff65", + "\udbff\udbff\udbff", + "\udc00\udc00\udc00", + "js\udbff", + "\udc00js", + "???", + "\root", + empty, + "AXS", + "dll", + "ocx", + "1", + "ps1", + "cmd", + "xpi", + "swf", + ]; + + const expectedTypes = [ + "text/plain", + "application/javascript", + "application/json", + "text/css", + "text/html", + "text/html", + "application/xhtml+xml", + "text/xml", + empty, + "image/png", + "image/apng", + "image/jpeg", + "image/jpeg", + "application/pdf", + empty, + "application/x-sh", + "application/octet-stream", + empty, + empty, + empty, + empty, + empty, + empty, + empty, + empty, + empty, + empty, + empty, + "application/olescript", + "application/x-msdownload", + "application/octet-stream", + empty, + empty, + "text/plain", + "application/x-xpinstall", + "application/x-shockwave-flash", + ]; + + Assert.equal(extensions.length, expectedTypes.length); + + await Promise.all( + extensions.map(async (ext, i) => { + const fileName = nameStem + "." + ext; + const fileHandle = await root.getFileHandle(fileName, allowCreate); + const fileObject = await fileHandle.getFile(); + Assert.equal(fileObject.name, fileHandle.name); + Assert.equal(fileObject.type, expectedTypes[i]); + }) + ); +}; + +exported_symbols.testContentTypeChangesOnMove = async function () { + const allowCreate = { create: true }; + const root = await navigator.storage.getDirectory(); + const oldName = "testFile.txt"; + const oldType = "text/plain"; + const subdir = await root.getDirectoryHandle("subdir", allowCreate); + + const testName = "testFile.json"; + const testType = "application/json"; + + const fileHandle = await root.getFileHandle(oldName, allowCreate); + + async function checkMove(newName, newType) { + Assert.equal(fileHandle.name, newName, "Has filename changed?"); + { + const fileObject = await fileHandle.getFile(); + Assert.equal(fileObject.name, newName, "Is the fileobject renamed?"); + Assert.equal(fileObject.type, newType, "Is the fileobject type updated?"); + } + } + + // No name change + await checkMove(oldName, oldType); + await fileHandle.move(subdir); + await checkMove(oldName, oldType); + await fileHandle.move(root, oldName); + await checkMove(oldName, oldType); + + // With name change + + async function testMoveCall(...combo) { + await fileHandle.move(...combo); + await checkMove(testName, testType); + await fileHandle.move(root, oldName); + await checkMove(oldName, oldType); + } + + await testMoveCall(subdir, testName); + await testMoveCall(root, testName); + await testMoveCall(testName); +}; + +for (const [key, value] of Object.entries(exported_symbols)) { + Object.defineProperty(value, "name", { + value: key, + writable: false, + }); +} diff --git a/dom/fs/test/common/test_fileSystemDirectoryHandle.js b/dom/fs/test/common/test_fileSystemDirectoryHandle.js new file mode 100644 index 0000000000..65a293cd28 --- /dev/null +++ b/dom/fs/test/common/test_fileSystemDirectoryHandle.js @@ -0,0 +1,180 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +exported_symbols.smokeTest = async function smokeTest() { + const storage = navigator.storage; + const subdirectoryNames = new Set(["Documents", "Downloads", "Music"]); + const allowCreate = { create: true }; + + { + let root = await storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + let it = await root.values(); + Assert.ok(!!it, "Does root have values iterator?"); + + let elem = await it.next(); + Assert.ok(elem.done, "Is root directory empty?"); + + for (let dirName of subdirectoryNames) { + await root.getDirectoryHandle(dirName, allowCreate); + Assert.ok(true, "Was it possible to add subdirectory " + dirName + "?"); + } + } + + { + let root = await storage.getDirectory(); + Assert.ok(root, "Can we refresh the root directory?"); + + let it = await root.values(); + Assert.ok(!!it, "Does root have values iterator?"); + + let hasElements = false; + let hangGuard = 0; + for await (let [key, elem] of root.entries()) { + Assert.ok(elem, "Is element not non-empty?"); + Assert.equal("directory", elem.kind, "Is found item a directory?"); + Assert.ok( + elem.name.length >= 1 && elem.name.match("^[A-Za-z]{1,64}"), + "Are names of the elements strings?" + ); + Assert.equal(key, elem.name); + Assert.ok(subdirectoryNames.has(elem.name), "Is name among known names?"); + hasElements = true; + ++hangGuard; + if (hangGuard == 10) { + break; // Exit if there is a hang + } + } + + Assert.ok(hasElements, "Is values container now non-empty?"); + Assert.equal(3, hangGuard, "Do we only have three elements?"); + + { + it = await root.values(); + Assert.ok(!!it, "Does root have values iterator?"); + let elem = await it.next(); + + await elem.value.getDirectoryHandle("Trash", allowCreate); + let subit = elem.value.values(); + Assert.ok(!!elem, "Is element not non-empty?"); + let subdirResult = await subit.next(); + let subdir = subdirResult.value; + Assert.ok(!!subdir, "Is element not non-empty?"); + Assert.equal("directory", subdir.kind, "Is found item a directory?"); + Assert.equal("Trash", subdir.name, "Is found item a directory?"); + } + + const wipeEverything = { recursive: true }; + for (let dirName of subdirectoryNames) { + await root.removeEntry(dirName, wipeEverything); + Assert.ok( + true, + "Was it possible to remove subdirectory " + dirName + "?" + ); + } + } + + { + let root = await storage.getDirectory(); + Assert.ok(root, "Can we refresh the root directory?"); + + let it = root.values(); + Assert.ok(!!it, "Does root have values iterator?"); + + let elem = await it.next(); + Assert.ok(elem.done, "Is root directory empty?"); + } +}; + +exported_symbols.quotaTest = async function () { + const storage = navigator.storage; + const allowCreate = { create: true }; + + { + let root = await storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + const fileHandle = await root.getFileHandle("test.txt", allowCreate); + Assert.ok(!!fileHandle, "Can we get file handle?"); + + const cachedOriginUsage = await Utils.getCachedOriginUsage(); + + const writable = await fileHandle.createWritable(); + Assert.ok(!!writable, "Can we create writable file stream?"); + + const buffer = new ArrayBuffer(42); + Assert.ok(!!buffer, "Can we create array buffer?"); + + const result = await writable.write(buffer); + Assert.equal(result, undefined, "Can we write entire buffer?"); + + await writable.close(); + + const cachedOriginUsage2 = await Utils.getCachedOriginUsage(); + + Assert.equal( + cachedOriginUsage2 - cachedOriginUsage, + buffer.byteLength, + "Is cached origin usage correct after writing?" + ); + + await root.removeEntry("test.txt"); + + const cachedOriginUsage3 = await Utils.getCachedOriginUsage(); + + Assert.equal( + cachedOriginUsage3, + cachedOriginUsage, + "Is cached origin usage correct after removing file?" + ); + } +}; + +exported_symbols.pagedIterationTest = async function () { + const root = await navigator.storage.getDirectory(); + + for await (let contentItem of root.keys()) { + await root.removeEntry(contentItem, { recursive: true }); + } + + const allowCreate = { create: true }; + + // When half of the buffer is iterated, a request for the second half is sent. + // We test that the this boundary is crossed smoothly. + // After the buffer is filled, a request for more items is sent. The + // items are placed in the first half of the buffer. + // This boundary should also be crossed without problems. + // Currently, the buffer is half-filled at 1024. + const itemBatch = 3 + 2 * 1024; + for (let i = 0; i <= itemBatch; ++i) { + await root.getDirectoryHandle("" + i, allowCreate); + } + + let result = 0; + let sum = 0; + const handles = new Set(); + let isUnique = true; + for await (let [key, elem] of root.entries()) { + result += key.length; + sum += parseInt(elem.name); + if (handles.has(key)) { + // Asserting here is slow and verbose + isUnique = false; + break; + } + handles.add(key); + } + Assert.ok(isUnique); + Assert.equal(result, 7098); + Assert.equal(sum, (itemBatch * (itemBatch + 1)) / 2); +}; + +for (const [key, value] of Object.entries(exported_symbols)) { + Object.defineProperty(value, "name", { + value: key, + writable: false, + }); +} diff --git a/dom/fs/test/common/test_syncAccessHandle.js b/dom/fs/test/common/test_syncAccessHandle.js new file mode 100644 index 0000000000..aaea95b20d --- /dev/null +++ b/dom/fs/test/common/test_syncAccessHandle.js @@ -0,0 +1,237 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const allowCreate = { create: true }; + +exported_symbols.test0 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + try { + await root.getFileHandle("test.txt"); + Assert.ok(false, "Opened file that shouldn't exist"); + } catch (e) { + dump("caught exception when we tried to open a non-existant file\n"); + } +}; + +exported_symbols.test1 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + const testFile = await root.getFileHandle("test.txt", allowCreate); + Assert.ok(!!testFile, "Can't create file"); + let handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create SyncAccessHandle"); + await handle.close(); + handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create second SyncAccessHandle to same file"); + await handle.close(); +}; + +exported_symbols.test2 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + const testFile = await root.getFileHandle("test.txt"); + Assert.ok(!!testFile, "Can't open file"); + let handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create SyncAccessHandle"); + await handle.close(); + + await root.removeEntry("test.txt"); + try { + handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Didn't remove file!"); + if (handle) { + await handle.close(); + } + } catch (e) { + dump("Caught exception trying to create accesshandle to deleted file\n"); + } +}; + +exported_symbols.test3 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(!!root, "Can we access the root directory?"); + + let dir = await root.getDirectoryHandle("dir", allowCreate); + Assert.ok(!!dir, "Can we create a directory?"); + + // XXX not implemented yet + //const path = await root.resolve(dir); + //Assert.ok(path == ["dir"], "Wrong path: " + path); + + let dir2 = await dir.getDirectoryHandle("dir", allowCreate); + Assert.ok(!!dir, "Can we create dir/dir?"); + + // XXX not implemented yet + //const path = await root.resolve(dir2); + //Assert.ok(path == ["dir", "dir"], "Wrong path: " + path); + + let dir3 = await dir.getDirectoryHandle("bar", allowCreate); + Assert.ok(!!dir3, "Can we create dir/bar?"); + + // This should fail + try { + await root.getDirectoryHandle("bar"); + Assert.ok(!dir, "we shouldn't be able to get bar unless we create it"); + } catch (e) { + dump("caught exception when we tried to get a non-existant dir\n"); + } + + const testFile = await dir2.getFileHandle("test.txt", allowCreate); + Assert.ok(!!testFile, "Can't create file in dir2"); + let handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create SyncAccessHandle in dir2"); + await handle.close(); +}; + +exported_symbols.test4 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(!!root, "Can we access the root directory?"); + + const testFile = await root.getFileHandle("test.txt", allowCreate); + Assert.ok(!!testFile, "Can't access existing file"); + let handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create SyncAccessHandle to existing file"); + + // Write a sentence to the end of the file. + const encoder = new TextEncoder(); + const writeBuffer = encoder.encode("Thank you for reading this."); + const writeSize = handle.write(writeBuffer); + Assert.ok(!!writeSize); + + // Read it back + // Get size of the file. + let fileSize = await handle.getSize(); + Assert.ok(fileSize == writeBuffer.byteLength); + // Read file content to a buffer. + const readBuffer = new ArrayBuffer(fileSize); + const readSize = handle.read(readBuffer, { at: 0 }); + Assert.ok(!!readSize); + //Assert.ok(readBuffer == writeBuffer); + + await handle.truncate(5); + fileSize = await handle.getSize(); + Assert.ok(fileSize == 5); + + await handle.flush(); + await handle.close(); +}; + +exported_symbols.test5 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(!!root, "Can we access the root directory?"); + + const testFile = await root.getFileHandle("test.txt"); + Assert.ok(!!testFile, "Can't create file"); + let handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create SyncAccessHandle"); + + try { + const testFile2 = await root.getFileHandle("test2.txt", allowCreate); + let handle2 = await testFile2.createSyncAccessHandle(); + Assert.ok(!!handle2, "can't create SyncAccessHandle to second file!"); + if (handle2) { + await handle2.close(); + } + } catch (e) { + Assert.ok(false, "Failed to create second file"); + } + + await handle.close(); +}; + +exported_symbols.test6 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + const testFile = await root.getFileHandle("test.txt"); + Assert.ok(!!testFile, "Can't get file"); + let handle = await testFile.createSyncAccessHandle(); + Assert.ok(!!handle, "Can't create SyncAccessHandle"); + + try { + let handle2 = await testFile.createSyncAccessHandle(); + Assert.ok(!handle2, "Shouldn't create SyncAccessHandle!"); + if (handle2) { + await handle2.close(); + } + } catch (e) { + // should always happen + dump("caught exception when we tried to get 2 SyncAccessHandles\n"); + } + + // test that locks work across multiple connections for an origin + try { + let root2 = await navigator.storage.getDirectory(); + Assert.ok(root2, "Can we access the root2 directory?"); + + const testFile2 = await root2.getFileHandle("test.txt"); + Assert.ok(!!testFile2, "Can't get file"); + let handle2 = await testFile2.createSyncAccessHandle(); + Assert.ok(!handle2, "Shouldn't create SyncAccessHandle (2)!"); + if (handle2) { + await handle2.close(); + } + } catch (e) { + // should always happen + dump("caught exception when we tried to get 2 SyncAccessHandles\n"); + } + + if (handle) { + await handle.close(); + } +}; + +exported_symbols.quotaTest = async function () { + const shrinkedStorageSizeKB = 5 * 1024; + const defaultDatabaseSize = 294912; + + // Shrink storage size to 5MB. + await Utils.shrinkStorageSize(shrinkedStorageSizeKB); + + let root = await navigator.storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + // Fill entire storage. + const fileHandle = await root.getFileHandle("test.txt"); + Assert.ok(!!fileHandle, "Can we get file handle?"); + + const accessHandle = await fileHandle.createSyncAccessHandle(); + Assert.ok(!!accessHandle, "Can we create sync access handle?"); + + const buffer = new ArrayBuffer( + shrinkedStorageSizeKB * 1024 - defaultDatabaseSize + ); + Assert.ok(!!buffer, "Can we create array buffer?"); + + const written = accessHandle.write(buffer); + Assert.equal(written, buffer.byteLength, "Can we write entire buffer?"); + + // Try to write one more byte. + const fileHandle2 = await root.getFileHandle("test2.txt"); + Assert.ok(!!fileHandle2, "Can we get file handle?"); + + const accessHandle2 = await fileHandle2.createSyncAccessHandle(); + Assert.ok(!!accessHandle2, "Can we create sync access handle?"); + + const buffer2 = new ArrayBuffer(1); + Assert.ok(!!buffer2, "Can we create array buffer?"); + + const written2 = accessHandle2.write(buffer2); + Assert.equal(written2, 0, "Can we write beyond the limit?"); + + await accessHandle.close(); + await accessHandle2.close(); + + await Utils.restoreStorageSize(); +}; + +for (const [key, value] of Object.entries(exported_symbols)) { + Object.defineProperty(value, "name", { + value: key, + writable: false, + }); +} diff --git a/dom/fs/test/common/test_writableFileStream.js b/dom/fs/test/common/test_writableFileStream.js new file mode 100644 index 0000000000..20226ff061 --- /dev/null +++ b/dom/fs/test/common/test_writableFileStream.js @@ -0,0 +1,93 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const allowCreate = { create: true }; + +exported_symbols.test0 = async function () { + let root = await navigator.storage.getDirectory(); + Assert.ok(!!root, "Can we access the root directory?"); + + const testFile = await root.getFileHandle("test.txt", allowCreate); + Assert.ok(!!testFile, "Can't access existing file"); + let writable = await testFile.createWritable(); + Assert.ok(!!writable, "Can't create WritableFileStream to existing file"); + + // Write a sentence to the end of the file. + const encoder = new TextEncoder(); + const writeBuffer = encoder.encode("Thank you for reading this."); + try { + dump("Trying to write...\n"); + await writable.write(writeBuffer); + dump("closing...\n"); + await writable.close(); + } catch (e) { + Assert.ok(false, "Couldn't write to WritableFileStream: " + e); + } + + // Read it back + // Get size of the file. + let file = await testFile.getFile(); + Assert.ok( + !!file, + "Can't create File to file written with WritableFileStream" + ); + let fileSize = file.size; + Assert.ok(fileSize == writeBuffer.byteLength); +}; + +exported_symbols.quotaTest = async function () { + const shrinkedStorageSizeKB = 5 * 1024; + const defaultDatabaseSize = 294912; + + // Shrink storage size to 5MB. + await Utils.shrinkStorageSize(shrinkedStorageSizeKB); + + let root = await navigator.storage.getDirectory(); + Assert.ok(root, "Can we access the root directory?"); + + // Fill entire storage. + const fileHandle = await root.getFileHandle("test.txt", allowCreate); + Assert.ok(!!fileHandle, "Can we get file handle?"); + + const writable = await fileHandle.createWritable(); + Assert.ok(!!writable, "Can we create writable file stream?"); + + const buffer = new ArrayBuffer( + shrinkedStorageSizeKB * 1024 - defaultDatabaseSize + ); + Assert.ok(!!buffer, "Can we create array buffer?"); + + const result = await writable.write(buffer); + Assert.equal(result, undefined, "Can we write entire buffer?"); + + // Try to write one more byte. + const fileHandle2 = await root.getFileHandle("test2.txt", allowCreate); + Assert.ok(!!fileHandle2, "Can we get file handle?"); + + const writable2 = await fileHandle2.createWritable(); + Assert.ok(!!writable2, "Can we create writable file stream?"); + + const buffer2 = new ArrayBuffer(1); + Assert.ok(!!buffer2, "Can we create array buffer?"); + + try { + await writable2.write(buffer2); + Assert.ok(false, "Should have thrown"); + } catch (ex) { + Assert.ok(true, "Did throw"); + Assert.ok(DOMException.isInstance(ex), "Threw DOMException"); + Assert.equal(ex.name, "QuotaExceededError", "Threw right DOMException"); + } + + await writable.close(); + // writable2 is already closed because of the failed write above + + await Utils.restoreStorageSize(); +}; + +for (const [key, value] of Object.entries(exported_symbols)) { + Object.defineProperty(value, "name", { + value: key, + writable: false, + }); +} diff --git a/dom/fs/test/common/xpcshell.ini b/dom/fs/test/common/xpcshell.ini new file mode 100644 index 0000000000..b63970a370 --- /dev/null +++ b/dom/fs/test/common/xpcshell.ini @@ -0,0 +1,13 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +support-files = + nsresult.js + test_basics.js + test_fileSystemDirectoryHandle.js + test_writableFileStream.js + +[dummy.js] +skip-if = true diff --git a/dom/fs/test/crashtests/1798773.html b/dom/fs/test/crashtests/1798773.html new file mode 100644 index 0000000000..893dfcfc59 --- /dev/null +++ b/dom/fs/test/crashtests/1798773.html @@ -0,0 +1,19 @@ +<script id="worker1" type="javascript/worker"> +self.onmessage = async function () { + const xhr = new XMLHttpRequest() + self.onerror = () => { + xhr.open("POST", "FOOBAR", false) + xhr.send() + } + self.reportError(undefined) + self.dir = await self.navigator.storage.getDirectory() +} +</script> +<script> +window.addEventListener('load', async () => { + const blob = new Blob([document.querySelector('#worker1').textContent], { type: "text/javascript" }) + let worker = new Worker(window.URL.createObjectURL(blob)) + worker.postMessage([], []) + setTimeout(() => {window.location.reload(true)}) +}) +</script> diff --git a/dom/fs/test/crashtests/1800470.html b/dom/fs/test/crashtests/1800470.html new file mode 100644 index 0000000000..a7d5dfa8bb --- /dev/null +++ b/dom/fs/test/crashtests/1800470.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<html class="reftest-wait"> +<head> + <script id="worker1" type="javascript/worker"> + self.onmessage = async function (e) { + const directory = await navigator.storage.getDirectory(); + const file = await directory.getFileHandle("500014c3-f683-4551-bb26-08025c9be332", { + create: true, + }); + const stream = await file.createWritable({}); + const regex = new RegExp(".*"); + await stream.abort(regex); + self.postMessage("done"); + self.close(); + } + </script> + <script> + var worker; + document.addEventListener('DOMContentLoaded', () => { + const buffer = new ArrayBuffer(1); + const blob = new Blob([document.querySelector('#worker1').textContent], { type: 'text/javascript' }); + worker = new Worker(window.URL.createObjectURL(blob)); + worker.postMessage([buffer], [buffer]); + worker.onmessage = function() {document.documentElement.removeAttribute("class"); } + }); + </script> +</head> +</html> diff --git a/dom/fs/test/crashtests/1809759.html b/dom/fs/test/crashtests/1809759.html new file mode 100644 index 0000000000..b9df8de02f --- /dev/null +++ b/dom/fs/test/crashtests/1809759.html @@ -0,0 +1,10 @@ +<script> +document.addEventListener('DOMContentLoaded', async () => { + document.location.search = '?' + let a = self.navigator.storage + let xhr = new XMLHttpRequest() + xhr.open('POST', 'FOOBAR', false) + xhr.send() + await a.getDirectory() +}) +</script> diff --git a/dom/fs/test/crashtests/1816710.html b/dom/fs/test/crashtests/1816710.html new file mode 100644 index 0000000000..f7641e009d --- /dev/null +++ b/dom/fs/test/crashtests/1816710.html @@ -0,0 +1,8 @@ +<script> + window.addEventListener('load', async () => { + const dir = await navigator.storage.getDirectory(); + const file = await dir.getFileHandle('555b8afb-96ac-4fe3-8cec', { create: true }); + const writable = await file.createWritable({}); + setTimeout('self.close()', 2000) + }) +</script> diff --git a/dom/fs/test/crashtests/1841702.html b/dom/fs/test/crashtests/1841702.html new file mode 100644 index 0000000000..0509972ae8 --- /dev/null +++ b/dom/fs/test/crashtests/1841702.html @@ -0,0 +1,16 @@ +<script id="worker1" type="javascript/worker"> +self.onmessage = async function(e) { + let a = await e.data[0].getFileHandle("c21deba4-fb73-4407-94f8-2e3782bf3f23", {"create": true}) + self.close() + await a.createWritable({}) +} +</script> + +<script> +window.addEventListener("load", async () => { + let a = await self.clientInformation.storage.getDirectory() + const blob = new Blob([document.querySelector('#worker1').textContent], { type: "text/javascript" }) + let worker = new Worker(window.URL.createObjectURL(blob)) + worker.postMessage([a], []) +}) +</script> diff --git a/dom/fs/test/crashtests/crashtests.list b/dom/fs/test/crashtests/crashtests.list new file mode 100644 index 0000000000..050ebca726 --- /dev/null +++ b/dom/fs/test/crashtests/crashtests.list @@ -0,0 +1,8 @@ +# StorageManager isn't enabled on Android +defaults skip-if(Android) pref(dom.fs.enabled,true) pref(dom.fs.writable_file_stream.enabled,true) + +load 1798773.html +load 1800470.html +load 1809759.html +load 1816710.html +load 1841702.html diff --git a/dom/fs/test/gtest/FileSystemMocks.cpp b/dom/fs/test/gtest/FileSystemMocks.cpp new file mode 100644 index 0000000000..1ad8e77c56 --- /dev/null +++ b/dom/fs/test/gtest/FileSystemMocks.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemMocks.h" + +#include <string> + +#include "ErrorList.h" +#include "gtest/gtest-assertion-result.h" +#include "js/RootingAPI.h" +#include "jsapi.h" +#include "mozilla/dom/FileSystemManager.h" +#include "nsContentUtils.h" +#include "nsISupports.h" + +namespace mozilla::dom::fs::test { + +nsIGlobalObject* GetGlobal() { + AutoJSAPI jsapi; + DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope()); + MOZ_ASSERT(ok); + + JSContext* cx = jsapi.cx(); + mozilla::dom::GlobalObject globalObject(cx, JS::CurrentGlobalOrNull(cx)); + nsCOMPtr<nsIGlobalObject> global = + do_QueryInterface(globalObject.GetAsSupports()); + MOZ_ASSERT(global); + + return global.get(); +} + +nsresult GetAsString(const RefPtr<Promise>& aPromise, nsAString& aString) { + AutoJSAPI jsapi; + DebugOnly<bool> ok = jsapi.Init(xpc::PrivilegedJunkScope()); + MOZ_ASSERT(ok); + + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSObject*> promiseObj(cx, aPromise->PromiseObj()); + JS::Rooted<JS::Value> vp(cx, JS::GetPromiseResult(promiseObj)); + + switch (aPromise->State()) { + case Promise::PromiseState::Pending: { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + case Promise::PromiseState::Resolved: { + if (nsContentUtils::StringifyJSON(cx, vp, aString, + UndefinedIsNullStringLiteral)) { + return NS_OK; + } + + return NS_ERROR_UNEXPECTED; + } + + case Promise::PromiseState::Rejected: { + if (vp.isInt32()) { + int32_t errorCode = vp.toInt32(); + aString.AppendInt(errorCode); + + return NS_OK; + } + + if (!vp.isObject()) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr<Exception> exception; + UNWRAP_OBJECT(Exception, &vp, exception); + if (!exception) { + return NS_ERROR_UNEXPECTED; + } + + aString.Append(NS_ConvertUTF8toUTF16( + GetStaticErrorName(static_cast<nsresult>(exception->Result())))); + + return NS_OK; + } + + default: + break; + } + + return NS_ERROR_FAILURE; +} + +mozilla::ipc::PrincipalInfo GetPrincipalInfo() { + return mozilla::ipc::PrincipalInfo{mozilla::ipc::SystemPrincipalInfo{}}; +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/FileSystemMocks.h b/dom/fs/test/gtest/FileSystemMocks.h new file mode 100644 index 0000000000..867302908b --- /dev/null +++ b/dom/fs/test/gtest/FileSystemMocks.h @@ -0,0 +1,343 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_TEST_GTEST_FILESYSTEMMOCKS_H_ +#define DOM_FS_TEST_GTEST_FILESYSTEMMOCKS_H_ + +#include <memory> // We don't have a mozilla shared pointer for pod types + +#include "TestHelpers.h" +#include "fs/FileSystemChildFactory.h" +#include "fs/FileSystemRequestHandler.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "js/Promise.h" +#include "js/RootingAPI.h" +#include "jsapi.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "nsIGlobalObject.h" +#include "nsISupports.h" +#include "nsISupportsImpl.h" +#include "nsITimer.h" + +namespace mozilla::dom::fs { + +inline std::ostream& operator<<(std::ostream& aOut, + const FileSystemEntryMetadata& aMetadata) { + return aOut; +} + +namespace test { + +nsIGlobalObject* GetGlobal(); + +nsresult GetAsString(const RefPtr<Promise>& aPromise, nsAString& aString); + +mozilla::ipc::PrincipalInfo GetPrincipalInfo(); + +class MockFileSystemRequestHandler : public FileSystemRequestHandler { + public: + MOCK_METHOD(void, GetRootHandle, + (RefPtr<FileSystemManager> aManager, RefPtr<Promise> aPromise, + ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetDirectoryHandle, + (RefPtr<FileSystemManager> & aManager, + const FileSystemChildMetadata& aDirectory, bool aCreate, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetFileHandle, + (RefPtr<FileSystemManager> & aManager, + const FileSystemChildMetadata& aFile, bool aCreate, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetFile, + (RefPtr<FileSystemManager> & aManager, + const FileSystemEntryMetadata& aFile, RefPtr<Promise> aPromise, + ErrorResult& aError), + (override)); + + MOCK_METHOD(void, GetEntries, + (RefPtr<FileSystemManager> & aManager, const EntryId& aDirectory, + PageNumber aPage, RefPtr<Promise> aPromise, + RefPtr<FileSystemEntryMetadataArray>& aSink, + ErrorResult& aError), + (override)); + + MOCK_METHOD(void, RemoveEntry, + (RefPtr<FileSystemManager> & aManager, + const FileSystemChildMetadata& aEntry, bool aRecursive, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, MoveEntry, + (RefPtr<FileSystemManager> & aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, + const FileSystemChildMetadata& aNewEntry, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, RenameEntry, + (RefPtr<FileSystemManager> & aManager, FileSystemHandle* aHandle, + const FileSystemEntryMetadata& aEntry, const Name& aName, + RefPtr<Promise> aPromise, ErrorResult& aError), + (override)); + + MOCK_METHOD(void, Resolve, + (RefPtr<FileSystemManager> & aManager, + const FileSystemEntryPair& aEndpoints, RefPtr<Promise> aPromise, + ErrorResult& aError), + (override)); +}; + +class WaitablePromiseListener { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + + virtual void ClearDone() = 0; + + virtual bool IsDone() const = 0; + + virtual PromiseNativeHandler* AsHandler() = 0; + + protected: + virtual ~WaitablePromiseListener() = default; +}; + +template <class SuccessHandler, class ErrorHandler, + uint32_t MilliSeconds = 2000u> +class TestPromiseListener : public PromiseNativeHandler, + public WaitablePromiseListener { + public: + TestPromiseListener() + : mIsDone(std::make_shared<bool>(false)), + mTimer(), + mOnSuccess(), + mOnError() { + ClearDone(); + } + + // nsISupports implementation + + NS_IMETHODIMP QueryInterface(REFNSIID aIID, void** aInstancePtr) override { + nsresult rv = NS_ERROR_UNEXPECTED; + NS_INTERFACE_TABLE0(TestPromiseListener) + + return rv; + } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TestPromiseListener, override) + + // PromiseNativeHandler implementation + + void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aError) override { + mozilla::ScopeExit flagAsDone([isDone = mIsDone, timer = mTimer] { + timer->Cancel(); + *isDone = true; + }); + + mOnSuccess(); + } + + void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue, + ErrorResult& aError) override { + mozilla::ScopeExit flagAsDone([isDone = mIsDone, timer = mTimer] { + timer->Cancel(); + *isDone = true; + }); + + if (aValue.isInt32()) { + mOnError(static_cast<nsresult>(aValue.toInt32())); + return; + } + + ASSERT_TRUE(aValue.isObject()); + JS::Rooted<JSObject*> exceptionObject(aCx, &aValue.toObject()); + + RefPtr<Exception> exception; + UNWRAP_OBJECT(Exception, exceptionObject, exception); + if (exception) { + mOnError(static_cast<nsresult>(exception->Result())); + return; + } + } + + // WaitablePromiseListener implementation + + void ClearDone() override { + *mIsDone = false; + if (mTimer) { + mTimer->Cancel(); + } + auto timerCallback = [isDone = mIsDone](nsITimer* aTimer) { + *isDone = true; + FAIL() << "Timed out!"; + }; + const char* timerName = "fs::TestPromiseListener::ClearDone"; + auto res = NS_NewTimerWithCallback(timerCallback, MilliSeconds, + nsITimer::TYPE_ONE_SHOT, timerName); + if (res.isOk()) { + mTimer = res.unwrap(); + } + } + + bool IsDone() const override { return *mIsDone; } + + PromiseNativeHandler* AsHandler() override { return this; } + + SuccessHandler& GetSuccessHandler() { return mOnSuccess; } + + SuccessHandler& GetErrorHandler() { return mOnError; } + + protected: + virtual ~TestPromiseListener() = default; + + std::shared_ptr<bool> mIsDone; // We pass this to a callback + + nsCOMPtr<nsITimer> mTimer; + + SuccessHandler mOnSuccess; + + ErrorHandler mOnError; +}; + +class TestFileSystemManagerChild : public FileSystemManagerChild { + public: + MOCK_METHOD(void, SendGetRootHandle, + (mozilla::ipc::ResolveCallback<FileSystemGetHandleResponse> && + aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetDirectoryHandle, + (const FileSystemGetHandleRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetHandleResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetFileHandle, + (const FileSystemGetHandleRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetHandleResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetAccessHandle, + (const FileSystemGetAccessHandleRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetAccessHandleResponse>&& + aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetWritable, + (const FileSystemGetWritableRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetWritableFileStreamResponse>&& + aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetFile, + (const FileSystemGetFileRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetFileResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendResolve, + (const FileSystemResolveRequest& request, + mozilla::ipc::ResolveCallback<FileSystemResolveResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendGetEntries, + (const FileSystemGetEntriesRequest& request, + mozilla::ipc::ResolveCallback<FileSystemGetEntriesResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD( + void, SendRemoveEntry, + (const FileSystemRemoveEntryRequest& request, + mozilla::ipc::ResolveCallback<FileSystemRemoveEntryResponse>&& aResolve, + mozilla::ipc::RejectCallback&& aReject), + (override)); + + MOCK_METHOD(void, Shutdown, (), (override)); + + protected: + virtual ~TestFileSystemManagerChild() = default; +}; + +class TestFileSystemChildFactory final : public FileSystemChildFactory { + public: + explicit TestFileSystemChildFactory(TestFileSystemManagerChild* aChild) + : mChild(aChild) {} + + already_AddRefed<FileSystemManagerChild> Create() const override { + return RefPtr<TestFileSystemManagerChild>(mChild).forget(); + } + + ~TestFileSystemChildFactory() = default; + + private: + TestFileSystemManagerChild* mChild; +}; + +struct MockExpectMe { + MOCK_METHOD0(InvokeMe, void()); + + template <class... Args> + void operator()(Args...) { + InvokeMe(); + } +}; + +template <nsresult Expected> +struct NSErrorMatcher { + void operator()(nsresult aErr) { ASSERT_NSEQ(Expected, aErr); } +}; + +struct FailOnCall { + template <class... Args> + void operator()(Args...) { + FAIL(); + } +}; + +} // namespace test +} // namespace mozilla::dom::fs + +#define MOCK_PROMISE_LISTENER(name, ...) \ + using name = mozilla::dom::fs::test::TestPromiseListener<__VA_ARGS__>; + +MOCK_PROMISE_LISTENER( + ExpectNotImplemented, mozilla::dom::fs::test::FailOnCall, + mozilla::dom::fs::test::NSErrorMatcher<NS_ERROR_NOT_IMPLEMENTED>); + +MOCK_PROMISE_LISTENER(ExpectResolveCalled, mozilla::dom::fs::test::MockExpectMe, + mozilla::dom::fs::test::FailOnCall); + +#endif // DOM_FS_TEST_GTEST_FILESYSTEMMOCKS_H_ diff --git a/dom/fs/test/gtest/TestHelpers.cpp b/dom/fs/test/gtest/TestHelpers.cpp new file mode 100644 index 0000000000..b028530f19 --- /dev/null +++ b/dom/fs/test/gtest/TestHelpers.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "TestHelpers.h" + +#include "gtest/gtest.h" +#include "mozilla/dom/quota/CommonMetadata.h" +#include "nsString.h" + +namespace testing::internal { + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsAString& s1, + const nsAString& s2) { + if (s1.Equals(s2)) { + return ::testing::AssertionSuccess(); + } + + return ::testing::internal::EqFailure( + s1_expression, s2_expression, + std::string(NS_ConvertUTF16toUTF8(s1).get()), + std::string(NS_ConvertUTF16toUTF8(s2).get()), + /* ignore case */ false); +} + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsACString& s1, + const nsACString& s2) { + if (s1.Equals(s2)) { + return ::testing::AssertionSuccess(); + } + + return ::testing::internal::EqFailure(s1_expression, s2_expression, + std::string(s1), std::string(s2), + /* ignore case */ false); +} + +} // namespace testing::internal + +namespace mozilla::dom::fs::test { + +quota::OriginMetadata GetTestOriginMetadata() { + return quota::OriginMetadata{""_ns, + "example.com"_ns, + "http://example.com"_ns, + "http://example.com"_ns, + /* aIsPrivate */ false, + quota::PERSISTENCE_TYPE_DEFAULT}; +} + +const Origin& GetTestOrigin() { + static const Origin origin = "http://example.com"_ns; + return origin; +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/TestHelpers.h b/dom/fs/test/gtest/TestHelpers.h new file mode 100644 index 0000000000..bfbcb9840c --- /dev/null +++ b/dom/fs/test/gtest/TestHelpers.h @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef DOM_FS_TEST_GTEST_TESTHELPERS_H_ +#define DOM_FS_TEST_GTEST_TESTHELPERS_H_ + +#include "ErrorList.h" +#include "gtest/gtest.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/QuotaCommon.h" + +namespace testing::internal { + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsAString& s1, + const nsAString& s2); + +GTEST_API_ ::testing::AssertionResult CmpHelperSTREQ(const char* s1_expression, + const char* s2_expression, + const nsACString& s1, + const nsACString& s2); + +} // namespace testing::internal + +#define ASSERT_NSEQ(lhs, rhs) \ + ASSERT_STREQ(GetStaticErrorName((lhs)), GetStaticErrorName((rhs))) + +#define TEST_TRY_UNWRAP_META(tempVar, target, expr) \ + auto MOZ_REMOVE_PAREN(tempVar) = (expr); \ + ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isOk()) \ + << GetStaticErrorName( \ + mozilla::ToNSResult(MOZ_REMOVE_PAREN(tempVar).unwrapErr())); \ + MOZ_REMOVE_PAREN(target) = MOZ_REMOVE_PAREN(tempVar).unwrap(); + +#define TEST_TRY_UNWRAP_ERR_META(tempVar, target, expr) \ + auto MOZ_REMOVE_PAREN(tempVar) = (expr); \ + ASSERT_TRUE(MOZ_REMOVE_PAREN(tempVar).isErr()); \ + MOZ_REMOVE_PAREN(target) = \ + mozilla::ToNSResult(MOZ_REMOVE_PAREN(tempVar).unwrapErr()); + +#define TEST_TRY_UNWRAP(target, expr) \ + TEST_TRY_UNWRAP_META(MOZ_UNIQUE_VAR(testVar), target, expr) + +#define TEST_TRY_UNWRAP_ERR(target, expr) \ + TEST_TRY_UNWRAP_ERR_META(MOZ_UNIQUE_VAR(testVar), target, expr) + +namespace mozilla::dom { + +namespace quota { + +struct OriginMetadata; + +} // namespace quota + +namespace fs::test { + +quota::OriginMetadata GetTestOriginMetadata(); + +const Origin& GetTestOrigin(); + +} // namespace fs::test +} // namespace mozilla::dom + +#endif // DOM_FS_TEST_GTEST_TESTHELPERS_H_ diff --git a/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp new file mode 100644 index 0000000000..9114f3bb33 --- /dev/null +++ b/dom/fs/test/gtest/api/TestFileSystemDirectoryHandle.cpp @@ -0,0 +1,234 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemMocks.h" +#include "gtest/gtest.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileSystemDirectoryHandle.h" +#include "mozilla/dom/FileSystemDirectoryHandleBinding.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/StorageManager.h" +#include "nsIGlobalObject.h" + +using ::testing::_; + +namespace mozilla::dom::fs::test { + +class TestFileSystemDirectoryHandle : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mRequestHandler = MakeUnique<MockFileSystemRequestHandler>(); + mMetadata = FileSystemEntryMetadata("dir"_ns, u"Directory"_ns, + /* directory */ true); + mName = u"testDir"_ns; + mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + + if (!mManager->IsShutdown()) { + mManager->Shutdown(); + } + } + + nsIGlobalObject* mGlobal = GetGlobal(); + const IterableIteratorBase::IteratorType mIteratorType = + IterableIteratorBase::IteratorType::Keys; + UniquePtr<MockFileSystemRequestHandler> mRequestHandler; + FileSystemEntryMetadata mMetadata; + nsString mName; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemDirectoryHandle, constructDirectoryHandleRefPointer) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); +} + +TEST_F(TestFileSystemDirectoryHandle, initIterator) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + RefPtr<FileSystemDirectoryHandle::iterator_t> iterator = + new FileSystemDirectoryHandle::iterator_t(dirHandle.get(), mIteratorType); + IgnoredErrorResult rv; + dirHandle->InitAsyncIteratorData(iterator->Data(), mIteratorType, rv); + ASSERT_TRUE(iterator->Data().mImpl); +} + +class MockFileSystemDirectoryIteratorImpl final + : public FileSystemDirectoryIterator::Impl { + public: + MOCK_METHOD(already_AddRefed<Promise>, Next, + (nsIGlobalObject * aGlobal, RefPtr<FileSystemManager>& aManager, + ErrorResult& aError), + (override)); +}; + +TEST_F(TestFileSystemDirectoryHandle, isNextPromiseReturned) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + auto mockIter = MakeUnique<MockFileSystemDirectoryIteratorImpl>(); + IgnoredErrorResult error; + EXPECT_CALL(*mockIter, Next(_, _, _)) + .WillOnce(::testing::Return(Promise::Create(mGlobal, error))); + + RefPtr<FileSystemDirectoryHandle::iterator_t> iterator = + MakeAndAddRef<FileSystemDirectoryHandle::iterator_t>(dirHandle.get(), + mIteratorType); + iterator->Data().mImpl = std::move(mockIter); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = + dirHandle->GetNextIterationResult(iterator.get(), rv); + ASSERT_TRUE(promise); +} + +TEST_F(TestFileSystemDirectoryHandle, isHandleKindDirectory) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + ASSERT_EQ(FileSystemHandleKind::Directory, dirHandle->Kind()); +} + +TEST_F(TestFileSystemDirectoryHandle, isFileHandleReturned) { + EXPECT_CALL(*mRequestHandler, GetFileHandle(_, _, _, _, _)) + .WillOnce(::testing::ReturnArg<3>()); + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + FileSystemGetFileOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetFileHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesGetFileHandleFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + FileSystemGetFileOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetFileHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemDirectoryHandle, isDirectoryHandleReturned) { + EXPECT_CALL(*mRequestHandler, GetDirectoryHandle(_, _, _, _, _)) + .WillOnce(::testing::ReturnArg<3>()); + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + FileSystemGetDirectoryOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetDirectoryHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesGetDirectoryHandleFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + FileSystemGetDirectoryOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->GetDirectoryHandle(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemDirectoryHandle, isRemoveEntrySuccessful) { + EXPECT_CALL(*mRequestHandler, RemoveEntry(_, _, _, _, _)) + .WillOnce(::testing::ReturnArg<3>()); + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + FileSystemRemoveOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->RemoveEntry(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesRemoveEntryFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + FileSystemRemoveOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->RemoveEntry(mName, options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemDirectoryHandle, isResolveSuccessful) { + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata, + mRequestHandler.release()); + + ASSERT_TRUE(dirHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->Resolve(*dirHandle, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemDirectoryHandle, doesResolveFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemDirectoryHandle> dirHandle = + MakeAndAddRef<FileSystemDirectoryHandle>(mGlobal, mManager, mMetadata); + + ASSERT_TRUE(dirHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->Resolve(*dirHandle, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/api/TestFileSystemFileHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemFileHandle.cpp new file mode 100644 index 0000000000..263c1f2ed1 --- /dev/null +++ b/dom/fs/test/gtest/api/TestFileSystemFileHandle.cpp @@ -0,0 +1,144 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemMocks.h" +#include "fs/FileSystemChildFactory.h" +#include "gtest/gtest.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileSystemFileHandle.h" +#include "mozilla/dom/FileSystemFileHandleBinding.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/StorageManager.h" +#include "nsIGlobalObject.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemFileHandle : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mRequestHandler = MakeUnique<MockFileSystemRequestHandler>(); + mMetadata = + FileSystemEntryMetadata("file"_ns, u"File"_ns, /* directory */ false); + mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + + if (!mManager->IsShutdown()) { + mManager->Shutdown(); + } + } + + nsIGlobalObject* mGlobal = GetGlobal(); + UniquePtr<MockFileSystemRequestHandler> mRequestHandler; + FileSystemEntryMetadata mMetadata; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemFileHandle, constructFileHandleRefPointer) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); +} + +TEST_F(TestFileSystemFileHandle, isHandleKindFile) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + ASSERT_EQ(FileSystemHandleKind::File, fileHandle->Kind()); +} + +TEST_F(TestFileSystemFileHandle, isFileReturned) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->GetFile(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemFileHandle, doesGetFileFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->GetFile(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemFileHandle, isWritableReturned) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + FileSystemCreateWritableOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateWritable(options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemFileHandle, doesCreateWritableFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + FileSystemCreateWritableOptions options; + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateWritable(options, rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +TEST_F(TestFileSystemFileHandle, isSyncAccessHandleReturned) { + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateSyncAccessHandle(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); +} + +TEST_F(TestFileSystemFileHandle, doesCreateSyncAccessHandleFailOnNullGlobal) { + mGlobal = nullptr; + RefPtr<FileSystemFileHandle> fileHandle = MakeAndAddRef<FileSystemFileHandle>( + mGlobal, mManager, mMetadata, mRequestHandler.release()); + + ASSERT_TRUE(fileHandle); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->CreateSyncAccessHandle(rv); + + ASSERT_TRUE(rv.ErrorCodeIs(NS_ERROR_UNEXPECTED)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/api/TestFileSystemHandle.cpp b/dom/fs/test/gtest/api/TestFileSystemHandle.cpp new file mode 100644 index 0000000000..19cdc98a84 --- /dev/null +++ b/dom/fs/test/gtest/api/TestFileSystemHandle.cpp @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemMocks.h" +#include "fs/FileSystemChildFactory.h" +#include "gtest/gtest.h" +#include "mozilla/dom/FileSystemDirectoryHandle.h" +#include "mozilla/dom/FileSystemFileHandle.h" +#include "mozilla/dom/FileSystemHandle.h" +#include "mozilla/dom/FileSystemHandleBinding.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/StorageManager.h" +#include "nsIGlobalObject.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemHandle : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mDirMetadata = FileSystemEntryMetadata("dir"_ns, u"Directory"_ns, + /* directory */ true); + mFileMetadata = + FileSystemEntryMetadata("file"_ns, u"File"_ns, /* directory */ false); + mManager = MakeAndAddRef<FileSystemManager>(mGlobal, nullptr); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + + if (!mManager->IsShutdown()) { + mManager->Shutdown(); + } + } + + nsIGlobalObject* mGlobal = GetGlobal(); + FileSystemEntryMetadata mDirMetadata; + FileSystemEntryMetadata mFileMetadata; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemHandle, createAndDestroyHandles) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + EXPECT_TRUE(dirHandle); + EXPECT_TRUE(fileHandle); +} + +TEST_F(TestFileSystemHandle, areFileNamesAsExpected) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + auto GetEntryName = [](const RefPtr<FileSystemHandle>& aHandle) { + DOMString domName; + aHandle->GetName(domName); + nsString result; + domName.ToString(result); + return result; + }; + + const nsString& dirName = GetEntryName(dirHandle); + EXPECT_TRUE(mDirMetadata.entryName().Equals(dirName)); + + const nsString& fileName = GetEntryName(fileHandle); + EXPECT_TRUE(mFileMetadata.entryName().Equals(fileName)); +} + +TEST_F(TestFileSystemHandle, isParentObjectReturned) { + ASSERT_TRUE(mGlobal); + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + + ASSERT_EQ(mGlobal, dirHandle->GetParentObject()); +} + +TEST_F(TestFileSystemHandle, areHandleKindsAsExpected) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + EXPECT_EQ(FileSystemHandleKind::Directory, dirHandle->Kind()); + EXPECT_EQ(FileSystemHandleKind::File, fileHandle->Kind()); +} + +TEST_F(TestFileSystemHandle, isDifferentEntry) { + RefPtr<FileSystemHandle> dirHandle = + new FileSystemDirectoryHandle(mGlobal, mManager, mDirMetadata); + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = dirHandle->IsSameEntry(*fileHandle, rv); + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); + ASSERT_TRUE(promise); + ASSERT_EQ(Promise::PromiseState::Resolved, promise->State()); + + nsString result; + ASSERT_NSEQ(NS_OK, GetAsString(promise, result)); + ASSERT_STREQ(u"false"_ns, result); +} + +TEST_F(TestFileSystemHandle, isSameEntry) { + RefPtr<FileSystemHandle> fileHandle = + new FileSystemFileHandle(mGlobal, mManager, mFileMetadata); + + IgnoredErrorResult rv; + RefPtr<Promise> promise = fileHandle->IsSameEntry(*fileHandle, rv); + ASSERT_TRUE(rv.ErrorCodeIs(NS_OK)); + ASSERT_TRUE(promise); + ASSERT_EQ(Promise::PromiseState::Resolved, promise->State()); + + nsString result; + ASSERT_NSEQ(NS_OK, GetAsString(promise, result)); + ASSERT_STREQ(u"true"_ns, result); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/api/moz.build b/dom/fs/test/gtest/api/moz.build new file mode 100644 index 0000000000..eb8416a3ba --- /dev/null +++ b/dom/fs/test/gtest/api/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES = [ + "TestFileSystemDirectoryHandle.cpp", + "TestFileSystemFileHandle.cpp", + "TestFileSystemHandle.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/api", + "/dom/fs/include", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/gtest/child/TestFileSystemBackgroundRequestHandler.cpp b/dom/fs/test/gtest/child/TestFileSystemBackgroundRequestHandler.cpp new file mode 100644 index 0000000000..48d63cfc36 --- /dev/null +++ b/dom/fs/test/gtest/child/TestFileSystemBackgroundRequestHandler.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemBackgroundRequestHandler.h" +#include "FileSystemMocks.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/PFileSystemManager.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemBackgroundRequestHandler : public ::testing::Test { + protected: + void SetUp() override { + // TODO: Fix the test to not depend on CreateFileSystemManagerParent + // failure because of the pref set to false. + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", false); + + mFileSystemManagerChild = MakeAndAddRef<TestFileSystemManagerChild>(); + } + + void TearDown() override { + nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + prefs->SetBoolPref("dom.fs.enabled", true); + } + + RefPtr<FileSystemBackgroundRequestHandler> + GetFileSystemBackgroundRequestHandler() { + return MakeRefPtr<FileSystemBackgroundRequestHandler>( + new TestFileSystemChildFactory(mFileSystemManagerChild)); + } + + mozilla::ipc::PrincipalInfo mPrincipalInfo = GetPrincipalInfo(); + RefPtr<TestFileSystemManagerChild> mFileSystemManagerChild; +}; + +TEST_F(TestFileSystemBackgroundRequestHandler, + isCreateFileSystemManagerChildSuccessful) { + EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) + .WillOnce([fileSystemManagerChild = + static_cast<void*>(mFileSystemManagerChild.get())]() { + static_cast<TestFileSystemManagerChild*>(fileSystemManagerChild) + ->FileSystemManagerChild::Shutdown(); + }); + + bool done = false; + auto testable = GetFileSystemBackgroundRequestHandler(); + testable->CreateFileSystemManagerChild(mPrincipalInfo) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](bool) { done = true; }, [&done](nsresult) { done = true; }); + // MozPromise should be rejected + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [&done]() { return done; }); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp b/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp new file mode 100644 index 0000000000..c2832103af --- /dev/null +++ b/dom/fs/test/gtest/child/TestFileSystemRequestHandler.cpp @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemBackgroundRequestHandler.h" +#include "FileSystemEntryMetadataArray.h" +#include "FileSystemMocks.h" +#include "fs/FileSystemRequestHandler.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/FileSystemManager.h" +#include "mozilla/dom/FileSystemManagerChild.h" +#include "mozilla/dom/IPCBlob.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/StorageManager.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/IPCCore.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" + +using ::testing::_; +using ::testing::ByRef; +using ::testing::Invoke; +using ::testing::Return; + +namespace mozilla::dom::fs::test { + +class TestFileSystemRequestHandler : public ::testing::Test { + protected: + void SetUp() override { + mListener = MakeAndAddRef<ExpectResolveCalled>(); + + mChild = FileSystemChildMetadata("parent"_ns, u"ChildName"_ns); + mEntry = FileSystemEntryMetadata("myid"_ns, u"EntryName"_ns, + /* directory */ false); + mName = u"testDir"_ns; + mFileSystemManagerChild = MakeAndAddRef<TestFileSystemManagerChild>(); + mManager = MakeAndAddRef<FileSystemManager>( + mGlobal, nullptr, + MakeRefPtr<FileSystemBackgroundRequestHandler>( + mFileSystemManagerChild)); + } + + void TearDown() override { + if (!mManager->IsShutdown()) { + EXPECT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + } + } + + already_AddRefed<Promise> GetDefaultPromise() { + IgnoredErrorResult rv; + RefPtr<Promise> result = Promise::Create(mGlobal, rv); + mListener->ClearDone(); + result->AppendNativeHandler(mListener->AsHandler()); + + return result.forget(); + } + + already_AddRefed<Promise> GetSimplePromise() { + IgnoredErrorResult rv; + RefPtr<Promise> result = Promise::Create(mGlobal, rv); + + return result.forget(); + } + + already_AddRefed<Promise> GetShutdownPromise() { + RefPtr<Promise> promise = GetDefaultPromise(); + EXPECT_CALL(*mFileSystemManagerChild, Shutdown()) + .WillOnce(Invoke([promise]() { promise->MaybeResolveWithUndefined(); })) + .WillOnce(Return()); + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + + return promise.forget(); + } + + UniquePtr<FileSystemRequestHandler> GetFileSystemRequestHandler() { + return MakeUnique<FileSystemRequestHandler>(); + } + + void ShutdownFileSystemManager() { + RefPtr<Promise> promise = GetShutdownPromise(); + + mManager->Shutdown(); + + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); + ASSERT_TRUE(mManager->IsShutdown()); + } + + nsIGlobalObject* mGlobal = GetGlobal(); + RefPtr<ExpectResolveCalled> mListener; + + FileSystemChildMetadata mChild; + FileSystemEntryMetadata mEntry; + nsString mName; + RefPtr<TestFileSystemManagerChild> mFileSystemManagerChild; + RefPtr<FileSystemManager> mManager; +}; + +TEST_F(TestFileSystemRequestHandler, isGetRootHandleSuccessful) { + auto fakeResponse = [](auto&& aResolve, auto&& /* aReject */) { + EntryId expected = "expected"_ns; + FileSystemGetHandleResponse response(expected); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetRootHandle(_, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetRootHandle(mManager, promise, IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetRootHandleBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetRootHandle(mManager, GetSimplePromise(), + error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetDirectoryHandleSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + EntryId expected = "expected"_ns; + FileSystemGetHandleResponse response(expected); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetDirectoryHandle(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetDirectoryHandle(mManager, mChild, + /* create */ true, promise, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetDirectoryHandleBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetDirectoryHandle( + mManager, mChild, /* aCreate */ true, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileHandleSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + EntryId expected = "expected"_ns; + FileSystemGetHandleResponse response(expected); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetFileHandle(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetFileHandle(mManager, mChild, /* create */ true, promise, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileHandleBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetFileHandle( + mManager, mChild, /* aCreate */ true, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + // We have to create a temporary file + nsCOMPtr<nsIFile> tmpfile; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpfile)); + ASSERT_EQ(NS_SUCCEEDED(rv), true); + + rv = tmpfile->AppendNative("GetFileTestBlob"_ns); + ASSERT_EQ(NS_SUCCEEDED(rv), true); + + rv = tmpfile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); + ASSERT_EQ(NS_SUCCEEDED(rv), true); + + auto blob = MakeRefPtr<FileBlobImpl>(tmpfile); + + TimeStamp last_modified_ms = 0; + ContentType type = "txt"_ns; + IPCBlob file; + IPCBlobUtils::Serialize(blob, file); + + nsTArray<Name> path; + path.AppendElement(u"root"_ns); + path.AppendElement(u"Trash"_ns); + + FileSystemFileProperties properties(last_modified_ms, file, type, path); + FileSystemGetFileResponse response(properties); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendGetFile(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + RefPtr<Promise> promise = GetDefaultPromise(); + auto testable = GetFileSystemRequestHandler(); + testable->GetFile(mManager, mEntry, promise, IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetFileBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetFile(mManager, mEntry, GetSimplePromise(), + error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetAccessHandleBlockedAfterShutdown) { + RefPtr<Promise> promise = GetShutdownPromise(); + + mManager->Shutdown(); + + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); + ASSERT_TRUE(mManager->IsShutdown()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetAccessHandle(mManager, mEntry, + GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetWritableBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetWritable( + mManager, mEntry, /* aKeepData */ false, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isGetEntriesSuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + nsTArray<FileSystemEntryMetadata> files; + nsTArray<FileSystemEntryMetadata> directories; + FileSystemDirectoryListing listing(files, directories); + FileSystemGetEntriesResponse response(listing); + aResolve(std::move(response)); + }; + + RefPtr<ExpectResolveCalled> listener = MakeAndAddRef<ExpectResolveCalled>(); + IgnoredErrorResult rv; + listener->ClearDone(); + EXPECT_CALL(listener->GetSuccessHandler(), InvokeMe()); + + RefPtr<Promise> promise = Promise::Create(mGlobal, rv); + promise->AppendNativeHandler(listener); + + EXPECT_CALL(*mFileSystemManagerChild, SendGetEntries(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + auto testable = GetFileSystemRequestHandler(); + RefPtr<FileSystemEntryMetadataArray> sink; + + testable->GetEntries(mManager, mEntry.entryId(), /* page */ 0, promise, sink, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [listener]() { return listener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isGetEntriesBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + RefPtr<FileSystemEntryMetadataArray> sink; + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->GetEntries(mManager, mEntry.entryId(), + /* aPage */ 0, GetSimplePromise(), + sink, error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +TEST_F(TestFileSystemRequestHandler, isRemoveEntrySuccessful) { + auto fakeResponse = [](const auto& /* aRequest */, auto&& aResolve, + auto&& /* aReject */) { + FileSystemRemoveEntryResponse response(mozilla::void_t{}); + aResolve(std::move(response)); + }; + + EXPECT_CALL(mListener->GetSuccessHandler(), InvokeMe()); + EXPECT_CALL(*mFileSystemManagerChild, SendRemoveEntry(_, _, _)) + .WillOnce(Invoke(fakeResponse)); + + auto testable = GetFileSystemRequestHandler(); + RefPtr<Promise> promise = GetDefaultPromise(); + testable->RemoveEntry(mManager, mChild, /* recursive */ true, promise, + IgnoredErrorResult()); + SpinEventLoopUntil("Promise is fulfilled or timeout"_ns, + [this]() { return mListener->IsDone(); }); +} + +TEST_F(TestFileSystemRequestHandler, isRemoveEntryBlockedAfterShutdown) { + ASSERT_NO_FATAL_FAILURE(ShutdownFileSystemManager()); + + IgnoredErrorResult error; + GetFileSystemRequestHandler()->RemoveEntry( + mManager, mChild, /* aRecursive */ true, GetSimplePromise(), error); + + ASSERT_TRUE(error.Failed()); + ASSERT_TRUE(error.ErrorCodeIs(NS_ERROR_ILLEGAL_DURING_SHUTDOWN)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/child/moz.build b/dom/fs/test/gtest/child/moz.build new file mode 100644 index 0000000000..c305ab1f2e --- /dev/null +++ b/dom/fs/test/gtest/child/moz.build @@ -0,0 +1,20 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES = [ + "TestFileSystemBackgroundRequestHandler.cpp", + "TestFileSystemRequestHandler.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/child", + "/dom/fs/include", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/gtest/moz.build b/dom/fs/test/gtest/moz.build new file mode 100644 index 0000000000..81be2a3d33 --- /dev/null +++ b/dom/fs/test/gtest/moz.build @@ -0,0 +1,25 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += [ + "api", + "child", + "parent", + "shared", +] + +UNIFIED_SOURCES = [ + "FileSystemMocks.cpp", + "TestHelpers.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/include", +] diff --git a/dom/fs/test/gtest/parent/TestFileSystemHashSource.cpp b/dom/fs/test/gtest/parent/TestFileSystemHashSource.cpp new file mode 100644 index 0000000000..2a680838a8 --- /dev/null +++ b/dom/fs/test/gtest/parent/TestFileSystemHashSource.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemHashSource.h" +#include "TestHelpers.h" +#include "gtest/gtest.h" +#include "mozilla/Array.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "nsContentUtils.h" +#include "nsLiteralString.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsTHashSet.h" + +namespace mozilla::dom::fs::test { + +using mozilla::dom::fs::data::FileSystemHashSource; + +namespace { + +constexpr size_t sha256ByteLength = 32u; + +constexpr size_t kExpectedLength = 52u; + +std::wstring asWide(const nsString& aStr) { + std::wstring result; + result.reserve(aStr.Length()); + for (const auto* it = aStr.BeginReading(); it != aStr.EndReading(); ++it) { + result.push_back(static_cast<wchar_t>(*it)); + } + return result; +} + +} // namespace + +TEST(TestFileSystemHashSource, isHashLengthAsExpected) +{ + EntryId parent = "a"_ns; + Name name = u"b"_ns; + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(parent, name)); + ASSERT_EQ(sha256ByteLength, result.Length()); +}; + +TEST(TestFileSystemHashSource, areNestedNameHashesValidAndUnequal) +{ + EntryId emptyParent = ""_ns; + Name name = u"a"_ns; + const size_t nestingNumber = 500u; + + nsTHashSet<EntryId> results; + nsTHashSet<Name> names; + + auto previousParent = emptyParent; + for (size_t i = 0; i < nestingNumber; ++i) { + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(previousParent, name)); + + TEST_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(result)); + + // Validity checks + ASSERT_TRUE(mozilla::IsAscii(encoded)) + << encoded; + Name upperCaseVersion; + nsContentUtils::ASCIIToUpper(encoded, upperCaseVersion); + ASSERT_STREQ(asWide(upperCaseVersion).c_str(), asWide(encoded).c_str()); + + // Is the same hash encountered? + ASSERT_FALSE(results.Contains(result)); + ASSERT_TRUE(results.Insert(result, mozilla::fallible)); + + // Is the same name encountered? + ASSERT_FALSE(names.Contains(encoded)); + ASSERT_TRUE(names.Insert(encoded, mozilla::fallible)); + + previousParent = result; + } +}; + +TEST(TestFileSystemHashSource, areNameCombinationHashesUnequal) +{ + EntryId emptyParent = ""_ns; + + mozilla::Array<Name, 2> inputs = {u"a"_ns, u"b"_ns}; + nsTArray<EntryId> results; + nsTArray<Name> names; + + for (const auto& name : inputs) { + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(emptyParent, name)); + TEST_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(result)); + + // Validity checks + ASSERT_TRUE(mozilla::IsAscii(encoded)) + << encoded; + Name upperCaseVersion; + nsContentUtils::ASCIIToUpper(encoded, upperCaseVersion); + ASSERT_STREQ(asWide(upperCaseVersion).c_str(), asWide(encoded).c_str()); + + results.AppendElement(result); + names.AppendElement(encoded); + } + + nsTArray<EntryId> more_results; + nsTArray<Name> more_names; + for (const auto& parent : results) { + for (const auto& name : inputs) { + TEST_TRY_UNWRAP(EntryId result, + FileSystemHashSource::GenerateHash(parent, name)); + TEST_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(result)); + + // Validity checks + ASSERT_TRUE(mozilla::IsAscii(encoded)) + << encoded; + Name upperCaseVersion; + nsContentUtils::ASCIIToUpper(encoded, upperCaseVersion); + ASSERT_STREQ(asWide(upperCaseVersion).c_str(), asWide(encoded).c_str()); + + more_results.AppendElement(result); + more_names.AppendElement(encoded); + } + } + + results.AppendElements(more_results); + names.AppendElements(more_names); + + // Is the same hash encountered? + for (size_t i = 0; i < results.Length(); ++i) { + for (size_t j = i + 1; j < results.Length(); ++j) { + ASSERT_STRNE(results[i].get(), results[j].get()); + } + } + + // Is the same name encountered? + for (size_t i = 0; i < names.Length(); ++i) { + for (size_t j = i + 1; j < names.Length(); ++j) { + ASSERT_STRNE(asWide(names[i]).c_str(), asWide(names[j]).c_str()); + } + } +}; + +TEST(TestFileSystemHashSource, encodeGeneratedHash) +{ + Name expected = u"HF6FOFV72G3NMDEJKYMVRIFJO4X5ZNZCF2GM7Q4Y5Q3E7NPQKSLA"_ns; + ASSERT_EQ(kExpectedLength, expected.Length()); + + EntryId parent = "a"_ns; + Name name = u"b"_ns; + TEST_TRY_UNWRAP(EntryId entry, + FileSystemHashSource::GenerateHash(parent, name)); + ASSERT_EQ(sha256ByteLength, entry.Length()); + + TEST_TRY_UNWRAP(Name result, FileSystemHashSource::EncodeHash(entry)); + ASSERT_EQ(kExpectedLength, result.Length()); + ASSERT_STREQ(asWide(expected).c_str(), asWide(result).c_str()); + + // Generate further hashes + TEST_TRY_UNWRAP(entry, FileSystemHashSource::GenerateHash(entry, result)); + ASSERT_EQ(sha256ByteLength, entry.Length()); + + TEST_TRY_UNWRAP(result, FileSystemHashSource::EncodeHash(entry)); + + // Always the same length + ASSERT_EQ(kExpectedLength, result.Length()); + + // Encoded versions should differ + ASSERT_STRNE(asWide(expected).c_str(), asWide(result).c_str()); + + // Padding length should have been stripped + char16_t padding = u"="_ns[0]; + const int32_t paddingStart = result.FindChar(padding); + ASSERT_EQ(-1, paddingStart); +}; + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/TestFileSystemQuotaClient.cpp b/dom/fs/test/gtest/parent/TestFileSystemQuotaClient.cpp new file mode 100644 index 0000000000..ee3347f973 --- /dev/null +++ b/dom/fs/test/gtest/parent/TestFileSystemQuotaClient.cpp @@ -0,0 +1,512 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemQuotaClient.h" +#include "TestHelpers.h" +#include "datamodel/FileSystemDataManager.h" +#include "datamodel/FileSystemDatabaseManager.h" +#include "datamodel/FileSystemFileManager.h" +#include "gtest/gtest.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/quota/FileStreams.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/QuotaManagerService.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/dom/quota/UsageInfo.h" +#include "mozilla/dom/quota/test/QuotaManagerDependencyFixture.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsIQuotaCallbacks.h" +#include "nsIQuotaRequests.h" +#include "nsNetUtil.h" +#include "nsScriptSecurityManager.h" + +namespace mozilla::dom::fs::test { + +quota::OriginMetadata GetTestQuotaOriginMetadata() { + return quota::OriginMetadata{""_ns, + "quotaexample.com"_ns, + "http://quotaexample.com"_ns, + "http://quotaexample.com"_ns, + /* aIsPrivate */ false, + quota::PERSISTENCE_TYPE_DEFAULT}; +} + +class TestFileSystemQuotaClient + : public quota::test::QuotaManagerDependencyFixture { + public: + // ExceedsPreallocation value may depend on platform and sqlite version! + static const int sExceedsPreallocation = 32 * 1024; + + protected: + void SetUp() override { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); } + + void TearDown() override { + EXPECT_NO_FATAL_FAILURE( + ClearStoragesForOrigin(GetTestQuotaOriginMetadata())); + ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); + } + + static void InitializeStorage() { + auto backgroundTask = []() { + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + ASSERT_TRUE(quotaManager); + + NS_DispatchAndSpinEventLoopUntilComplete( + "TestFileSystemQuotaClient"_ns, quotaManager->IOThread(), + NS_NewRunnableFunction("TestFileSystemQuotaClient", []() { + quota::QuotaManager* qm = quota::QuotaManager::Get(); + ASSERT_TRUE(qm); + + ASSERT_NSEQ(NS_OK, qm->EnsureStorageIsInitialized()); + + ASSERT_NSEQ(NS_OK, qm->EnsureTemporaryStorageIsInitialized()); + + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + + auto dirInfoRes = qm->EnsureTemporaryOriginIsInitialized( + quota::PERSISTENCE_TYPE_DEFAULT, testOriginMeta); + if (dirInfoRes.isErr()) { + ASSERT_NSEQ(NS_OK, dirInfoRes.unwrapErr()); + } + + qm->EnsureQuotaForOrigin(testOriginMeta); + })); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); + } + + static const Name& GetTestFileName() { + static Name testFileName = []() { + nsCString testCFileName; + testCFileName.SetLength(sExceedsPreallocation); + std::fill(testCFileName.BeginWriting(), testCFileName.EndWriting(), 'x'); + return NS_ConvertASCIItoUTF16(testCFileName.BeginReading(), + sExceedsPreallocation); + }(); + + return testFileName; + } + + static uint64_t BytesOfName(const Name& aName) { + return static_cast<uint64_t>(aName.Length() * sizeof(Name::char_type)); + } + + static const nsCString& GetTestData() { + static const nsCString sTestData = "There is a way out of every box"_ns; + return sTestData; + } + + static void CreateNewEmptyFile( + data::FileSystemDatabaseManager* const aDatabaseManager, + const FileSystemChildMetadata& aFileSlot, EntryId& aFileId) { + // The file should not exist yet + Result<EntryId, QMResult> existingTestFile = + aDatabaseManager->GetOrCreateFile(aFileSlot, sContentType, + /* create */ false); + ASSERT_TRUE(existingTestFile.isErr()); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, + ToNSResult(existingTestFile.unwrapErr())); + + // Create a new file + TEST_TRY_UNWRAP(aFileId, aDatabaseManager->GetOrCreateFile( + aFileSlot, sContentType, /* create */ true)); + } + + static void WriteDataToFile( + data::FileSystemDatabaseManager* const aDatabaseManager, + const EntryId& aFileId, const nsCString& aData) { + ContentType type; + TimeStamp lastModMilliS = 0; + Path path; + nsCOMPtr<nsIFile> fileObj; + + ASSERT_NSEQ(NS_OK, aDatabaseManager->GetFile(aFileId, type, lastModMilliS, + path, fileObj)); + + uint32_t written = 0; + ASSERT_NE(written, aData.Length()); + + const quota::OriginMetadata& testOriginMeta = GetTestQuotaOriginMetadata(); + + TEST_TRY_UNWRAP(nsCOMPtr<nsIOutputStream> fileStream, + quota::CreateFileOutputStream( + quota::PERSISTENCE_TYPE_DEFAULT, testOriginMeta, + quota::Client::FILESYSTEM, fileObj)); + + auto finallyClose = MakeScopeExit( + [&fileStream]() { ASSERT_NSEQ(NS_OK, fileStream->Close()); }); + ASSERT_NSEQ(NS_OK, + fileStream->Write(aData.get(), aData.Length(), &written)); + + ASSERT_EQ(aData.Length(), written); + } + + /* Static for use in callbacks */ + static void CreateRegisteredDataManager( + Registered<data::FileSystemDataManager>& aRegisteredDataManager) { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestQuotaOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&aRegisteredDataManager, + &done](Registered<data::FileSystemDataManager> + registeredDataManager) mutable { + auto doneOnReturn = MakeScopeExit([&done]() { done = true; }); + + ASSERT_TRUE(registeredDataManager->IsOpen()); + aRegisteredDataManager = std::move(registeredDataManager); + }, + [&done](nsresult rejectValue) { + auto doneOnReturn = MakeScopeExit([&done]() { done = true; }); + + ASSERT_NSEQ(NS_OK, rejectValue); + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + + ASSERT_TRUE(aRegisteredDataManager); + ASSERT_TRUE(aRegisteredDataManager->IsOpen()); + ASSERT_TRUE(aRegisteredDataManager->MutableDatabaseManagerPtr()); + } + + static void CheckUsageEqualTo(const quota::UsageInfo& aUsage, + uint64_t expected) { + EXPECT_TRUE(aUsage.FileUsage().isNothing()); + auto dbUsage = aUsage.DatabaseUsage(); + ASSERT_TRUE(dbUsage.isSome()); + const auto actual = dbUsage.value(); + ASSERT_EQ(actual, expected); + } + + static void CheckUsageGreaterThan(const quota::UsageInfo& aUsage, + uint64_t expected) { + EXPECT_TRUE(aUsage.FileUsage().isNothing()); + auto dbUsage = aUsage.DatabaseUsage(); + ASSERT_TRUE(dbUsage.isSome()); + const auto actual = dbUsage.value(); + ASSERT_GT(actual, expected); + } + + static ContentType sContentType; +}; + +ContentType TestFileSystemQuotaClient::sContentType; + +TEST_F(TestFileSystemQuotaClient, CheckUsageBeforeAnyFilesOnDisk) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + auto ioTask = [&isCanceled](const RefPtr<quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + ASSERT_FALSE(isCanceled); + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + const Origin& testOrigin = testOriginMeta.mOrigin; + + // After initialization, + // * database size is not zero + // * GetUsageForOrigin and InitOrigin should agree + TEST_TRY_UNWRAP(quota::UsageInfo usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageGreaterThan(usageNow, 0u)); + const auto initialDbUsage = usageNow.DatabaseUsage().value(); + + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, initialDbUsage)); + + // Create a new file + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId testFileId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, testFileId)); + + // After a new file has been created (only in the database), + // * database size has increased + // * GetUsageForOrigin and InitOrigin should agree + const auto expectedUse = initialDbUsage + BytesOfName(GetTestFileName()); + + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedUse)); + + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedUse)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // For uninitialized database, file usage is nothing + auto checkTask = + [&isCanceled](const RefPtr<mozilla::dom::quota::Client>& quotaClient) { + TEST_TRY_UNWRAP(quota::UsageInfo usageNow, + quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + GetTestQuotaOriginMetadata(), isCanceled)); + + ASSERT_TRUE(usageNow.DatabaseUsage().isNothing()); + EXPECT_TRUE(usageNow.FileUsage().isNothing()); + }; + + PerformOnIOThread(std::move(checkTask), + RefPtr<mozilla::dom::quota::Client>{quotaClient}); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + // Run tests with an initialized database + PerformOnIOThread(std::move(ioTask), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemQuotaClient, WritesToFilesShouldIncreaseUsage) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + auto ioTask = [&isCanceled]( + const RefPtr<mozilla::dom::quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + const Origin& testOrigin = testOriginMeta.mOrigin; + + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId testFileId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, testFileId)); + // const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + TEST_TRY_UNWRAP( + quota::UsageInfo usageNow, + quotaClient->GetUsageForOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_TRUE(usageNow.DatabaseUsage().isSome()); + const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + + // Fill the file with some content + const nsCString& testData = GetTestData(); + const auto expectedTotalUsage = testFileDbUsage + testData.Length(); + + ASSERT_NO_FATAL_FAILURE(WriteDataToFile(dbm, testFileId, testData)); + + // In this test we don't lock the file -> no rescan is expected + // and InitOrigin should return the previous value + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + + // When data manager unlocks the file, it should call update + // but in this test we call it directly + ASSERT_NSEQ(NS_OK, dbm->UpdateUsage(testFileId)); + + // Disk usage should have increased after writing + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedTotalUsage)); + + // The usage values should now agree + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedTotalUsage)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // Storage initialization + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + // Run tests with an initialized database + PerformOnIOThread(std::move(ioTask), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemQuotaClient, TrackedFilesOnInitOriginShouldCauseRescan) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + EntryId* testFileId = new EntryId(); + auto cleanupFileId = MakeScopeExit([&testFileId] { delete testFileId; }); + + auto fileCreation = [&testFileId](data::FileSystemDatabaseManager* dbm) { + const Origin& testOrigin = GetTestQuotaOriginMetadata().mOrigin; + + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId someId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, someId)); + testFileId->Append(someId); + }; + + auto writingToFile = + [&isCanceled, testFileId]( + const RefPtr<mozilla::dom::quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + TEST_TRY_UNWRAP( + quota::UsageInfo usageNow, + quotaClient->GetUsageForOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_TRUE(usageNow.DatabaseUsage().isSome()); + const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + // Fill the file with some content + const auto& testData = GetTestData(); + const auto expectedTotalUsage = testFileDbUsage + testData.Length(); + + ASSERT_NO_FATAL_FAILURE(WriteDataToFile(dbm, *testFileId, testData)); + + // We don't call update now - because the file is tracked and + // InitOrigin should perform a rescan + TEST_TRY_UNWRAP( + usageNow, quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE( + CheckUsageEqualTo(usageNow, expectedTotalUsage)); + + // As always, the cached and scanned values should agree + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE( + CheckUsageEqualTo(usageNow, expectedTotalUsage)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // Storage initialization + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + PerformOnIOThread(std::move(fileCreation), + rdm->MutableDatabaseManagerPtr()); + + // This should force a rescan + ASSERT_NSEQ(NS_OK, rdm->LockExclusive(*testFileId)); + PerformOnIOThread(std::move(writingToFile), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemQuotaClient, RemovingFileShouldDecreaseUsage) { + auto backgroundTask = []() { + mozilla::Atomic<bool> isCanceled{false}; + auto ioTask = [&isCanceled]( + const RefPtr<mozilla::dom::quota::Client>& quotaClient, + data::FileSystemDatabaseManager* dbm) { + const quota::OriginMetadata& testOriginMeta = + GetTestQuotaOriginMetadata(); + const Origin& testOrigin = testOriginMeta.mOrigin; + + TEST_TRY_UNWRAP(const EntryId rootId, data::GetRootHandle(testOrigin)); + FileSystemChildMetadata fileData(rootId, GetTestFileName()); + + EntryId testFileId; + ASSERT_NO_FATAL_FAILURE(CreateNewEmptyFile(dbm, fileData, testFileId)); + TEST_TRY_UNWRAP(quota::UsageInfo usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_TRUE(usageNow.DatabaseUsage().isSome()); + const auto testFileDbUsage = usageNow.DatabaseUsage().value(); + + // Fill the file with some content + const nsCString& testData = GetTestData(); + const auto expectedTotalUsage = testFileDbUsage + testData.Length(); + + ASSERT_NO_FATAL_FAILURE(WriteDataToFile(dbm, testFileId, testData)); + + // Currently, usage is expected to be updated on unlock by data manager + // but here UpdateUsage() is called directly + ASSERT_NSEQ(NS_OK, dbm->UpdateUsage(testFileId)); + + // At least some file disk usage should have appeared after unlocking + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, expectedTotalUsage)); + + TEST_TRY_UNWRAP(bool wasRemoved, + dbm->RemoveFile({rootId, GetTestFileName()})); + ASSERT_TRUE(wasRemoved); + + // Removes cascade and usage table should be up to date immediately + TEST_TRY_UNWRAP(usageNow, + quotaClient->InitOrigin(quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + + // GetUsageForOrigin should agree + TEST_TRY_UNWRAP(usageNow, quotaClient->GetUsageForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, + testOriginMeta, isCanceled)); + + ASSERT_NO_FATAL_FAILURE(CheckUsageEqualTo(usageNow, testFileDbUsage)); + }; + + RefPtr<mozilla::dom::quota::Client> quotaClient = fs::CreateQuotaClient(); + ASSERT_TRUE(quotaClient); + + // Storage initialization + ASSERT_NO_FATAL_FAILURE(InitializeStorage()); + + // Initialize database + Registered<data::FileSystemDataManager> rdm; + ASSERT_NO_FATAL_FAILURE(CreateRegisteredDataManager(rdm)); + + // Run tests with an initialized database + PerformOnIOThread(std::move(ioTask), std::move(quotaClient), + rdm->MutableDatabaseManagerPtr()); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManager.cpp b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManager.cpp new file mode 100644 index 0000000000..3082508540 --- /dev/null +++ b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManager.cpp @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FileSystemDataManager.h" +#include "TestHelpers.h" +#include "mozilla/MozPromise.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "mozilla/dom/quota/test/QuotaManagerDependencyFixture.h" + +namespace mozilla::dom::fs::test { + +class TestFileSystemDataManager + : public quota::test::QuotaManagerDependencyFixture { + public: + static void SetUpTestCase() { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); } + + static void TearDownTestCase() { ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); } +}; + +TEST_F(TestFileSystemDataManager, GetOrCreateFileSystemDataManager) { + auto backgroundTask = []() { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](Registered<data::FileSystemDataManager> registeredDataManager) { + RefPtr<data::FileSystemDataManager> dataManager = + registeredDataManager.get(); + + registeredDataManager = nullptr; + + return dataManager->OnClose(); + }, + [](nsresult rejectValue) { + return BoolPromise::CreateAndReject(rejectValue, __func__); + }) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue&) { done = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemDataManager, + GetOrCreateFileSystemDataManager_PendingOpen) { + auto backgroundTask = []() { + Registered<data::FileSystemDataManager> rdm1; + + Registered<data::FileSystemDataManager> rdm2; + + { + bool done1 = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&rdm1, &done1](Registered<data::FileSystemDataManager> + registeredDataManager) { + ASSERT_TRUE(registeredDataManager->IsOpen()); + + rdm1 = std::move(registeredDataManager); + + done1 = true; + }, + [&done1](nsresult rejectValue) { done1 = true; }); + + bool done2 = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&rdm2, &done2](Registered<data::FileSystemDataManager> + registeredDataManager) { + ASSERT_TRUE(registeredDataManager->IsOpen()); + + rdm2 = std::move(registeredDataManager); + + done2 = true; + }, + [&done2](nsresult rejectValue) { done2 = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, + [&done1, &done2]() { return done1 && done2; }); + } + + RefPtr<data::FileSystemDataManager> dm1 = rdm1.unwrap(); + + RefPtr<data::FileSystemDataManager> dm2 = rdm2.unwrap(); + + { + bool done1 = false; + + dm1->OnClose()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done1](const BoolPromise::ResolveOrRejectValue&) { done1 = true; }); + + bool done2 = false; + + dm2->OnClose()->Then( + GetCurrentSerialEventTarget(), __func__, + [&done2](const BoolPromise::ResolveOrRejectValue&) { done2 = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, + [&done1, &done2]() { return done1 && done2; }); + } + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +TEST_F(TestFileSystemDataManager, + GetOrCreateFileSystemDataManager_PendingClose) { + auto backgroundTask = []() { + Registered<data::FileSystemDataManager> rdm; + + { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&rdm, &done](Registered<data::FileSystemDataManager> + registeredDataManager) { + ASSERT_TRUE(registeredDataManager->IsOpen()); + + rdm = std::move(registeredDataManager); + + done = true; + }, + [&done](nsresult rejectValue) { done = true; }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + } + + RefPtr<data::FileSystemDataManager> dm = rdm.unwrap(); + + Unused << dm; + + { + bool done = false; + + data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + GetTestOriginMetadata()) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](Registered<data::FileSystemDataManager> + registeredDataManager) { + RefPtr<data::FileSystemDataManager> dataManager = + registeredDataManager.get(); + + registeredDataManager = nullptr; + + return dataManager->OnClose(); + }, + [](nsresult rejectValue) { + return BoolPromise::CreateAndReject(rejectValue, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, + [&done](const BoolPromise::ResolveOrRejectValue&) { + done = true; + }); + + SpinEventLoopUntil("Promise is fulfilled"_ns, [&done]() { return done; }); + } + }; + + PerformOnBackgroundThread(std::move(backgroundTask)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManagerVersion001.cpp b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManagerVersion001.cpp new file mode 100644 index 0000000000..f08132221b --- /dev/null +++ b/dom/fs/test/gtest/parent/datamodel/TestFileSystemDataManagerVersion001.cpp @@ -0,0 +1,921 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include <algorithm> + +#include "ErrorList.h" +#include "FileSystemDataManager.h" +#include "FileSystemDatabaseManagerVersion001.h" +#include "FileSystemFileManager.h" +#include "FileSystemHashSource.h" +#include "ResultStatement.h" +#include "SchemaVersion001.h" +#include "TestHelpers.h" +#include "gtest/gtest.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozStorageHelper.h" +#include "mozilla/Array.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Result.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/PFileSystemManager.h" +#include "mozilla/dom/quota/CommonMetadata.h" +#include "mozilla/dom/quota/test/QuotaManagerDependencyFixture.h" +#include "nsContentUtils.h" +#include "nsIFile.h" +#include "nsLiteralString.h" +#include "nsNetCID.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsStringFwd.h" +#include "nsTArray.h" +#include "nsTHashSet.h" + +namespace mozilla::dom::fs::test { + +using data::FileSystemDatabaseManagerVersion001; +using data::FileSystemFileManager; + +// This is a minimal mock to allow us to safely call the lock methods +// while avoiding assertions +class MockFileSystemDataManager final : public data::FileSystemDataManager { + public: + MockFileSystemDataManager(const quota::OriginMetadata& aOriginMetadata, + MovingNotNull<nsCOMPtr<nsIEventTarget>> aIOTarget, + MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue) + : FileSystemDataManager(aOriginMetadata, nullptr, std::move(aIOTarget), + std::move(aIOTaskQueue)) {} + + virtual ~MockFileSystemDataManager() { + // Need to avoid assertions + mState = State::Closed; + } +}; + +static void MakeDatabaseManagerVersion001( + RefPtr<MockFileSystemDataManager>& aDataManager, + FileSystemDatabaseManagerVersion001*& aDatabaseManager) { + TEST_TRY_UNWRAP(auto storageService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<mozIStorageService>, + MOZ_SELECT_OVERLOAD(do_GetService), + MOZ_STORAGE_SERVICE_CONTRACTID)); + + const auto flags = mozIStorageService::CONNECTION_DEFAULT; + ResultConnection connection; + + nsresult rv = storageService->OpenSpecialDatabase(kMozStorageMemoryStorageKey, + VoidCString(), flags, + getter_AddRefs(connection)); + ASSERT_NSEQ(NS_OK, rv); + + const Origin& testOrigin = GetTestOrigin(); + + TEST_TRY_UNWRAP( + DatabaseVersion version, + SchemaVersion001::InitializeConnection(connection, testOrigin)); + ASSERT_EQ(1, version); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + auto fmRes = FileSystemFileManager::CreateFileSystemFileManager( + GetTestOriginMetadata()); + ASSERT_FALSE(fmRes.isErr()); + + QM_TRY_UNWRAP(auto streamTransportService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>, + MOZ_SELECT_OVERLOAD(do_GetService), + NS_STREAMTRANSPORTSERVICE_CONTRACTID), + QM_VOID); + + quota::OriginMetadata originMetadata = GetTestOriginMetadata(); + + nsCString taskQueueName("OPFS "_ns + originMetadata.mOrigin); + + RefPtr<TaskQueue> ioTaskQueue = + TaskQueue::Create(do_AddRef(streamTransportService), taskQueueName.get()); + + aDataManager = MakeRefPtr<MockFileSystemDataManager>( + originMetadata, WrapMovingNotNull(streamTransportService), + WrapMovingNotNull(ioTaskQueue)); + + aDatabaseManager = new FileSystemDatabaseManagerVersion001( + aDataManager, std::move(connection), + MakeUnique<FileSystemFileManager>(fmRes.unwrap()), rootId); +} + +class TestFileSystemDatabaseManagerVersion001 + : public quota::test::QuotaManagerDependencyFixture { + public: + void SetUp() override { ASSERT_NO_FATAL_FAILURE(InitializeFixture()); } + + void TearDown() override { + ASSERT_NO_FATAL_FAILURE(ClearStoragesForOrigin(GetTestOriginMetadata())); + ASSERT_NO_FATAL_FAILURE(ShutdownFixture()); + } + + static ContentType sContentType; +}; + +ContentType TestFileSystemDatabaseManagerVersion001::sContentType = "psid"_ns; + +TEST_F(TestFileSystemDatabaseManagerVersion001, + smokeTestCreateRemoveDirectories) { + auto ioTask = []() { + nsresult rv = NS_OK; + // Ensure that FileSystemDataManager lives for the lifetime of the test + RefPtr<MockFileSystemDataManager> dataManager; + FileSystemDatabaseManagerVersion001* rdm = nullptr; + ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(dataManager, rdm)); + UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm); + // if any of these exit early, we have to close + auto autoClose = MakeScopeExit([rdm] { rdm->Close(); }); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns); + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP(EntryId firstChild, dm->GetOrCreateDirectory( + firstChildMeta, /* create */ true)); + + int32_t dbVersion = 0; + TEST_TRY_UNWRAP(FileSystemDirectoryListing entries, + dm->GetDirectoryEntries(rootId, dbVersion)); + ASSERT_EQ(1u, entries.directories().Length()); + ASSERT_EQ(0u, entries.files().Length()); + + const auto& firstItemRef = entries.directories()[0]; + ASSERT_TRUE(u"First"_ns == firstItemRef.entryName()) + << firstItemRef.entryName(); + ASSERT_EQ(firstChild, firstItemRef.entryId()); + + TEST_TRY_UNWRAP( + EntryId firstChildClone, + dm->GetOrCreateDirectory(firstChildMeta, /* create */ true)); + ASSERT_EQ(firstChild, firstChildClone); + + FileSystemChildMetadata secondChildMeta(firstChild, u"Second"_ns); + TEST_TRY_UNWRAP( + EntryId secondChild, + dm->GetOrCreateDirectory(secondChildMeta, /* create */ true)); + + FileSystemEntryPair shortPair(firstChild, secondChild); + TEST_TRY_UNWRAP(Path shortPath, dm->Resolve(shortPair)); + ASSERT_EQ(1u, shortPath.Length()); + ASSERT_EQ(u"Second"_ns, shortPath[0]); + + FileSystemEntryPair longPair(rootId, secondChild); + TEST_TRY_UNWRAP(Path longPath, dm->Resolve(longPair)); + ASSERT_EQ(2u, longPath.Length()); + ASSERT_EQ(u"First"_ns, longPath[0]); + ASSERT_EQ(u"Second"_ns, longPath[1]); + + FileSystemEntryPair wrongPair(secondChild, rootId); + TEST_TRY_UNWRAP(Path emptyPath, dm->Resolve(wrongPair)); + ASSERT_TRUE(emptyPath.IsEmpty()); + + PageNumber page = 0; + TEST_TRY_UNWRAP(FileSystemDirectoryListing fEntries, + dm->GetDirectoryEntries(firstChild, page)); + ASSERT_EQ(1u, fEntries.directories().Length()); + ASSERT_EQ(0u, fEntries.files().Length()); + + const auto& secItemRef = fEntries.directories()[0]; + ASSERT_TRUE(u"Second"_ns == secItemRef.entryName()) + << secItemRef.entryName(); + ASSERT_EQ(secondChild, secItemRef.entryId()); + + TEST_TRY_UNWRAP_ERR( + rv, dm->RemoveDirectory(firstChildMeta, /* recursive */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + + TEST_TRY_UNWRAP(bool isDeleted, + dm->RemoveDirectory(firstChildMeta, /* recursive */ true)); + ASSERT_TRUE(isDeleted); + + FileSystemChildMetadata thirdChildMeta(secondChild, u"Second"_ns); + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(thirdChildMeta, /* create */ true)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error? + + dm->Close(); + }; + + PerformOnIOThread(std::move(ioTask)); +} + +TEST_F(TestFileSystemDatabaseManagerVersion001, smokeTestCreateRemoveFiles) { + auto ioTask = []() { + nsresult rv = NS_OK; + // Ensure that FileSystemDataManager lives for the lifetime of the test + RefPtr<MockFileSystemDataManager> datamanager; + FileSystemDatabaseManagerVersion001* rdm = nullptr; + ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(datamanager, rdm)); + UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns); + // If creating is not allowed, getting a file from empty root fails + TEST_TRY_UNWRAP_ERR(rv, dm->GetOrCreateFile(firstChildMeta, sContentType, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + // Creating a file under empty root succeeds + TEST_TRY_UNWRAP( + EntryId firstChild, + dm->GetOrCreateFile(firstChildMeta, sContentType, /* create */ true)); + + // Second time, the same file is returned + TEST_TRY_UNWRAP( + EntryId firstChildClone, + dm->GetOrCreateFile(firstChildMeta, sContentType, /* create */ true)); + ASSERT_EQ(firstChild, firstChildClone); + + // Directory listing returns the created file + PageNumber page = 0; + TEST_TRY_UNWRAP(FileSystemDirectoryListing entries, + dm->GetDirectoryEntries(rootId, page)); + ASSERT_EQ(0u, entries.directories().Length()); + ASSERT_EQ(1u, entries.files().Length()); + + auto& firstItemRef = entries.files()[0]; + ASSERT_TRUE(u"First"_ns == firstItemRef.entryName()) + << firstItemRef.entryName(); + ASSERT_EQ(firstChild, firstItemRef.entryId()); + + ContentType type; + TimeStamp lastModifiedMilliSeconds; + Path path; + nsCOMPtr<nsIFile> file; + rv = dm->GetFile(firstItemRef.entryId(), type, lastModifiedMilliSeconds, + path, file); + ASSERT_NSEQ(NS_OK, rv); + + ASSERT_STREQ(sContentType, type); + + const int64_t nowMilliSeconds = PR_Now() / 1000; + ASSERT_GE(nowMilliSeconds, lastModifiedMilliSeconds); + const int64_t expectedMaxDelayMilliSeconds = 100; + const int64_t actualDelay = nowMilliSeconds - lastModifiedMilliSeconds; + ASSERT_LT(actualDelay, expectedMaxDelayMilliSeconds); + + ASSERT_EQ(1u, path.Length()); + ASSERT_STREQ(u"First"_ns, path[0]); + + ASSERT_NE(nullptr, file); + + // Getting the file entry as directory fails + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + // Getting or creating the file entry as directory also fails + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory(firstChildMeta, /* create */ true)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + // Creating a file with non existing parent hash fails + + EntryId notAChildHash = "0123456789abcdef0123456789abcdef"_ns; + FileSystemChildMetadata notAChildMeta(notAChildHash, u"Dummy"_ns); + TEST_TRY_UNWRAP_ERR(rv, dm->GetOrCreateFile(notAChildMeta, sContentType, + /* create */ true)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error? + + // We create a directory under root + FileSystemChildMetadata secondChildMeta(rootId, u"Second"_ns); + TEST_TRY_UNWRAP( + EntryId secondChild, + dm->GetOrCreateDirectory(secondChildMeta, /* create */ true)); + + // The root should now contain the existing file and the new directory + TEST_TRY_UNWRAP(FileSystemDirectoryListing fEntries, + dm->GetDirectoryEntries(rootId, page)); + ASSERT_EQ(1u, fEntries.directories().Length()); + ASSERT_EQ(1u, fEntries.files().Length()); + + const auto& secItemRef = fEntries.directories()[0]; + ASSERT_TRUE(u"Second"_ns == secItemRef.entryName()) + << secItemRef.entryName(); + ASSERT_EQ(secondChild, secItemRef.entryId()); + + // Create a file under the new directory + FileSystemChildMetadata thirdChildMeta(secondChild, u"Third"_ns); + TEST_TRY_UNWRAP( + EntryId thirdChild, + dm->GetOrCreateFile(thirdChildMeta, sContentType, /* create */ true)); + + FileSystemEntryPair entryPair(rootId, thirdChild); + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve(entryPair)); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_EQ(u"Second"_ns, entryPath[0]); + ASSERT_EQ(u"Third"_ns, entryPath[1]); + + // If recursion is not allowed, the non-empty new directory may not be + // removed + TEST_TRY_UNWRAP_ERR( + rv, dm->RemoveDirectory(secondChildMeta, /* recursive */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + + // If recursion is allowed, the new directory goes away. + TEST_TRY_UNWRAP(bool isDeleted, + dm->RemoveDirectory(secondChildMeta, /* recursive */ true)); + ASSERT_TRUE(isDeleted); + + // The file under the removed directory is no longer accessible. + TEST_TRY_UNWRAP_ERR(rv, dm->GetOrCreateFile(thirdChildMeta, sContentType, + /* create */ true)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); // Is this a good error? + + // The deletion is reflected by the root directory listing + TEST_TRY_UNWRAP(FileSystemDirectoryListing nEntries, + dm->GetDirectoryEntries(rootId, 0)); + ASSERT_EQ(0u, nEntries.directories().Length()); + ASSERT_EQ(1u, nEntries.files().Length()); + + const auto& fileItemRef = nEntries.files()[0]; + ASSERT_TRUE(u"First"_ns == fileItemRef.entryName()) + << fileItemRef.entryName(); + ASSERT_EQ(firstChild, fileItemRef.entryId()); + + dm->Close(); + }; + + PerformOnIOThread(std::move(ioTask)); +} + +TEST_F(TestFileSystemDatabaseManagerVersion001, + smokeTestCreateMoveDirectories) { + auto ioTask = []() { + // Ensure that FileSystemDataManager lives for the lifetime of the test + RefPtr<MockFileSystemDataManager> datamanager; + FileSystemDatabaseManagerVersion001* rdm = nullptr; + ASSERT_NO_FATAL_FAILURE(MakeDatabaseManagerVersion001(datamanager, rdm)); + UniquePtr<FileSystemDatabaseManagerVersion001> dm(rdm); + auto closeAtExit = MakeScopeExit([&dm]() { dm->Close(); }); + + TEST_TRY_UNWRAP(EntryId rootId, data::GetRootHandle(GetTestOrigin())); + + FileSystemEntryMetadata rootMeta{rootId, u"root"_ns, + /* is directory */ true}; + + { + // Sanity check: no existing items should be found + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, /* page */ 0u)); + ASSERT_TRUE(contents.directories().IsEmpty()); + ASSERT_TRUE(contents.files().IsEmpty()); + } + + // Create subdirectory + FileSystemChildMetadata firstChildMeta(rootId, u"First"_ns); + TEST_TRY_UNWRAP( + EntryId firstChildDir, + dm->GetOrCreateDirectory(firstChildMeta, /* create */ true)); + + { + // Check that directory listing is as expected + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, /* page */ 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildMeta.childName(), + contents.directories()[0].entryName()); + } + + { + // Try to move subdirectory to its current location + FileSystemEntryMetadata src{firstChildDir, firstChildMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{rootId, src.entryName()}; + TEST_TRY_UNWRAP(bool moved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(moved); + } + + { + // Try to move subdirectory under itself + FileSystemEntryMetadata src{firstChildDir, firstChildMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{src.entryId(), src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + } + + { + // Try to move root under its subdirectory + FileSystemEntryMetadata src{rootId, rootMeta.entryName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + + // Create subsubdirectory + FileSystemChildMetadata firstChildDescendantMeta(firstChildDir, + u"Descendant"_ns); + TEST_TRY_UNWRAP(EntryId firstChildDescendant, + dm->GetOrCreateDirectory(firstChildDescendantMeta, + /* create */ true)); + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, /* page */ 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildDescendantMeta.childName(), + contents.directories()[0].entryName()); + + TEST_TRY_UNWRAP( + Path subSubDirPath, + dm->Resolve({rootId, contents.directories()[0].entryId()})); + ASSERT_EQ(2u, subSubDirPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), subSubDirPath[0]); + ASSERT_STREQ(firstChildDescendantMeta.childName(), subSubDirPath[1]); + } + + { + // Try to move subsubdirectory to its current location + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, src.entryName()}; + TEST_TRY_UNWRAP(bool moved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(moved); + } + + { + // Try to move subsubdirectory under itself + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{src.entryId(), src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + } + + { + // Try to move subdirectory under its descendant + FileSystemEntryMetadata src{firstChildDir, firstChildMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDescendant, src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_INVALID_MODIFICATION_ERR, rv); + } + + { + // Try to move root under its subsubdirectory + FileSystemEntryMetadata src{rootId, rootMeta.entryName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDescendant, src.entryName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + + // Create file in the subdirectory with already existing subsubdirectory + FileSystemChildMetadata testFileMeta(firstChildDir, u"Subfile"_ns); + TEST_TRY_UNWRAP( + EntryId testFile, + dm->GetOrCreateFile(testFileMeta, sContentType, /* create */ true)); + + // Get handles to the original locations of the entries + FileSystemEntryMetadata subSubDir; + FileSystemEntryMetadata subSubFile; + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, /* page */ 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_EQ(1u, contents.directories().Length()); + + subSubDir = contents.directories()[0]; + ASSERT_STREQ(firstChildDescendantMeta.childName(), subSubDir.entryName()); + + subSubFile = contents.files()[0]; + ASSERT_STREQ(testFileMeta.childName(), subSubFile.entryName()); + ASSERT_EQ(testFile, subSubFile.entryId()); + } + + { + TEST_TRY_UNWRAP(Path entryPath, + dm->Resolve({rootId, subSubFile.entryId()})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + } + + { + // Try to move file to its current location + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, src.entryName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Try to move subsubdirectory under a file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{testFile, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->MoveEntry(src, dest)); + ASSERT_NSEQ(NS_ERROR_STORAGE_CONSTRAINT, rv); + } + + { + // Silently overwrite a directory with a file using rename + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + const FileSystemChildMetadata& dest = firstChildDescendantMeta; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move file back and recreate the directory + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, testFileMeta)); + ASSERT_TRUE(isMoved); + + TEST_TRY_UNWRAP(EntryId firstChildDescendantCheck, + dm->GetOrCreateDirectory(firstChildDescendantMeta, + /* create */ true)); + ASSERT_EQ(firstChildDescendant, firstChildDescendantCheck); + } + + { + // Try to rename directory to quietly overwrite a file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + const FileSystemChildMetadata& dest = testFileMeta; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move directory back and recreate the file + FileSystemEntryMetadata src{firstChildDescendant, + testFileMeta.childName(), + /* is directory */ true}; + + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + TEST_TRY_UNWRAP( + EntryId testFileCheck, + dm->GetOrCreateFile(testFileMeta, sContentType, /* create */ true)); + ASSERT_EQ(testFile, testFileCheck); + } + + { + // Move file one level up + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, src.entryName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Check that listings are as expected + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildDescendantMeta.childName(), + contents.directories()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_STREQ(testFileMeta.childName(), contents.files()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(Path entryPath, + dm->Resolve({rootId, subSubFile.entryId()})); + ASSERT_EQ(1u, entryPath.Length()); + ASSERT_STREQ(testFileMeta.childName(), entryPath[0]); + } + + { + // Try to get a handle to the old item + TEST_TRY_UNWRAP_ERR( + nsresult rv, + dm->GetOrCreateFile(testFileMeta, sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + + { + // Try to move + rename file one level down to collide with a + // subSubDirectory, silently overwriting it + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Restore filename, move file to its original location and recreate + // the overwritten directory + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, testFileMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + FileSystemChildMetadata oldLocation{firstChildDir, + firstChildDescendantMeta.childName()}; + + // Is there still something out there? + TEST_TRY_UNWRAP_ERR( + nsresult rv, + dm->GetOrCreateFile(oldLocation, sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP(EntryId firstChildDescendantCheck, + dm->GetOrCreateDirectory(oldLocation, /* create */ true)); + ASSERT_EQ(firstChildDescendant, firstChildDescendantCheck); + } + + // Rename file first and then try to move it to collide with + // subSubDirectory, silently overwriting it + { + // Rename + FileSystemEntryMetadata src{testFile, testFileMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Try to move one level down + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move the file back and recreate the directory + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{rootId, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + FileSystemChildMetadata oldLocation{firstChildDir, + firstChildDescendantMeta.childName()}; + + // Is there still something out there? + TEST_TRY_UNWRAP_ERR( + nsresult rv, + dm->GetOrCreateFile(oldLocation, sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP(EntryId firstChildDescendantCheck, + dm->GetOrCreateDirectory(oldLocation, /* create */ true)); + ASSERT_EQ(firstChildDescendant, firstChildDescendantCheck); + } + + { + // Try to move subSubDirectory one level up to quietly overwrite a file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{rootId, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Move subSubDirectory back one level down and recreate the file + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + + FileSystemChildMetadata oldLocation{rootId, + firstChildDescendantMeta.childName()}; + + // We should no longer find anything there + TEST_TRY_UNWRAP_ERR(nsresult rv, dm->GetOrCreateDirectory( + oldLocation, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP( + EntryId testFileCheck, + dm->GetOrCreateFile(oldLocation, sContentType, /* create */ true)); + ASSERT_NE(testFile, testFileCheck); + testFile = testFileCheck; + } + + // Create a new file in the subsubdirectory + FileSystemChildMetadata newFileMeta{firstChildDescendant, + testFileMeta.childName()}; + TEST_TRY_UNWRAP( + EntryId newFile, + dm->GetOrCreateFile(newFileMeta, sContentType, /* create */ true)); + + { + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, newFile})); + ASSERT_EQ(3u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(firstChildDescendantMeta.childName(), entryPath[1]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[2]); + } + + { + // Move subSubDirectory one level up and rename it to testFile's old name + FileSystemEntryMetadata src{firstChildDescendant, + firstChildDescendantMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{rootId, testFileMeta.childName()}; + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Try to get handles to the moved items + TEST_TRY_UNWRAP_ERR(nsresult rv, + dm->GetOrCreateDirectory(firstChildDescendantMeta, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + // Still under the same parent + TEST_TRY_UNWRAP( + EntryId handle, + dm->GetOrCreateFile(newFileMeta, sContentType, /* create */ false)); + ASSERT_EQ(handle, newFile); + + TEST_TRY_UNWRAP( + handle, dm->GetOrCreateDirectory({rootId, testFileMeta.childName()}, + /* create */ false)); + ASSERT_EQ(handle, firstChildDescendant); + } + + { + // Check that new file path is as expected + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, newFile})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(testFileMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + } + + // Move first file and subSubDirectory back one level down keeping the names + { + FileSystemEntryMetadata src{testFile, + firstChildDescendantMeta.childName(), + /* is directory */ false}; + FileSystemChildMetadata dest{firstChildDir, + firstChildDescendantMeta.childName()}; + + // Flag is ignored + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + { + // Then move the directory + FileSystemEntryMetadata src{firstChildDescendant, + testFileMeta.childName(), + /* is directory */ true}; + FileSystemChildMetadata dest{firstChildDir, testFileMeta.childName()}; + + // Flag is ignored + TEST_TRY_UNWRAP(bool isMoved, dm->MoveEntry(src, dest)); + ASSERT_TRUE(isMoved); + } + + // Check that listings are as expected + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(rootId, 0u)); + ASSERT_TRUE(contents.files().IsEmpty()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildMeta.childName(), + contents.directories()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDir, 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_EQ(1u, contents.directories().Length()); + ASSERT_STREQ(firstChildDescendantMeta.childName(), + contents.files()[0].entryName()); + ASSERT_STREQ(testFileMeta.childName(), + contents.directories()[0].entryName()); + } + + { + TEST_TRY_UNWRAP(FileSystemDirectoryListing contents, + dm->GetDirectoryEntries(firstChildDescendant, 0u)); + ASSERT_EQ(1u, contents.files().Length()); + ASSERT_TRUE(contents.directories().IsEmpty()); + ASSERT_STREQ(testFileMeta.childName(), contents.files()[0].entryName()); + } + + // Names are swapped + { + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, testFile})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(firstChildDescendantMeta.childName(), entryPath[1]); + } + + { + TEST_TRY_UNWRAP(Path entryPath, + dm->Resolve({rootId, subSubDir.entryId()})); + ASSERT_EQ(2u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + } + + { + // Check that new file path is also as expected + TEST_TRY_UNWRAP(Path entryPath, dm->Resolve({rootId, newFile})); + ASSERT_EQ(3u, entryPath.Length()); + ASSERT_STREQ(firstChildMeta.childName(), entryPath[0]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[1]); + ASSERT_STREQ(testFileMeta.childName(), entryPath[2]); + } + + { + // Try to get handles to the old items + TEST_TRY_UNWRAP_ERR( + nsresult rv, dm->GetOrCreateFile({rootId, testFileMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, + dm->GetOrCreateFile({rootId, firstChildDescendantMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory({rootId, testFileMeta.childName()}, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR(rv, + dm->GetOrCreateDirectory( + {rootId, firstChildDescendantMeta.childName()}, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateFile({firstChildDir, testFileMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateDirectory( + {firstChildDir, firstChildDescendantMeta.childName()}, + /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_TYPE_MISMATCH_ERR, rv); + + TEST_TRY_UNWRAP_ERR( + rv, dm->GetOrCreateFile({testFile, newFileMeta.childName()}, + sContentType, /* create */ false)); + ASSERT_NSEQ(NS_ERROR_DOM_NOT_FOUND_ERR, rv); + } + }; + + PerformOnIOThread(std::move(ioTask)); +} + +} // namespace mozilla::dom::fs::test diff --git a/dom/fs/test/gtest/parent/datamodel/moz.build b/dom/fs/test/gtest/parent/datamodel/moz.build new file mode 100644 index 0000000000..a1a75c7b65 --- /dev/null +++ b/dom/fs/test/gtest/parent/datamodel/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES = [ + "TestFileSystemDataManager.cpp", + "TestFileSystemDataManagerVersion001.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/include", + "/dom/fs/parent", + "/dom/fs/parent/datamodel", + "/dom/fs/test/gtest", + "/dom/fs/test/gtest/parent", +] diff --git a/dom/fs/test/gtest/parent/moz.build b/dom/fs/test/gtest/parent/moz.build new file mode 100644 index 0000000000..9197c6ade2 --- /dev/null +++ b/dom/fs/test/gtest/parent/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += ["datamodel"] + +UNIFIED_SOURCES = [ + "TestFileSystemHashSource.cpp", + "TestFileSystemQuotaClient.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/parent", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/gtest/shared/TestFileSystemHelpers.cpp b/dom/fs/test/gtest/shared/TestFileSystemHelpers.cpp new file mode 100644 index 0000000000..ed38026634 --- /dev/null +++ b/dom/fs/test/gtest/shared/TestFileSystemHelpers.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "gtest/gtest.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/FileSystemHelpers.h" + +namespace mozilla::dom::fs { + +namespace { + +class TestObject { + public: + explicit TestObject(uint32_t aExpectedAddRefCnt = 0, + uint32_t aExpectedAddRegCnt = 0) + : mExpectedAddRefCnt(aExpectedAddRefCnt), + mExpectedAddRegCnt(aExpectedAddRegCnt), + mAddRefCnt(0), + mAddRegCnt(0), + mRefCnt(0), + mRegCnt(0), + mClosed(false) {} + + uint32_t AddRef() { + mRefCnt++; + mAddRefCnt++; + return mRefCnt; + } + + uint32_t Release() { + EXPECT_TRUE(mRefCnt > 0); + mRefCnt--; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + void Register() { + EXPECT_FALSE(mClosed); + mRegCnt++; + mAddRegCnt++; + } + + void Unregister() { + EXPECT_FALSE(mClosed); + EXPECT_TRUE(mRegCnt > 0); + mRegCnt--; + if (mRegCnt == 0) { + mClosed = true; + } + } + + void Foo() const {} + + private: + ~TestObject() { + if (mExpectedAddRefCnt > 0) { + EXPECT_EQ(mAddRefCnt, mExpectedAddRefCnt); + } + if (mExpectedAddRegCnt > 0) { + EXPECT_EQ(mAddRegCnt, mExpectedAddRegCnt); + } + } + + uint32_t mExpectedAddRefCnt; + uint32_t mExpectedAddRegCnt; + uint32_t mAddRefCnt; + uint32_t mAddRegCnt; + uint32_t mRefCnt; + uint32_t mRegCnt; + bool mClosed; +}; + +} // namespace + +TEST(TestFileSystemHelpers_Registered, Construct_Default) +{ + { Registered<TestObject> testObject; } +} + +TEST(TestFileSystemHelpers_Registered, Construct_Copy) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(2, 2)); + Registered<TestObject> testObject2(testObject1); + testObject2 = nullptr; + } +} + +TEST(TestFileSystemHelpers_Registered, Construct_Move) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + Registered<TestObject> testObject2(std::move(testObject1)); + } +} + +TEST(TestFileSystemHelpers_Registered, Construct_FromRefPtr) +{ + { Registered<TestObject> testObject(MakeRefPtr<TestObject>(1, 1)); } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Assign_FromNullPtr) +{ + { + Registered<TestObject> testObject; + testObject = nullptr; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Assign_Copy) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(2, 2)); + Registered<TestObject> testObject2; + testObject2 = testObject1; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Assign_Move) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + Registered<TestObject> testObject2; + testObject2 = std::move(testObject1); + } +} + +TEST(TestFileSystemHelpers_Registered, Method_Inspect) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + const RefPtr<TestObject>& testObject2 = testObject1.inspect(); + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Method_Unwrap) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + RefPtr<TestObject> testObject2 = testObject1.unwrap(); + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Method_Get) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + TestObject* testObject2 = testObject1.get(); + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Conversion_ToRawPtr) +{ + { + Registered<TestObject> testObject1(MakeRefPtr<TestObject>(1, 1)); + TestObject* testObject2 = testObject1; + Unused << testObject2; + } +} + +TEST(TestFileSystemHelpers_Registered, Operator_Dereference_ToRawPtr) +{ + { + Registered<TestObject> testObject(MakeRefPtr<TestObject>(1, 1)); + testObject->Foo(); + } +} + +} // namespace mozilla::dom::fs diff --git a/dom/fs/test/gtest/shared/moz.build b/dom/fs/test/gtest/shared/moz.build new file mode 100644 index 0000000000..e8feae8c57 --- /dev/null +++ b/dom/fs/test/gtest/shared/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, you can obtain one at http://mozilla.org/MPL/2.0/. + +UNIFIED_SOURCES = [ + "TestFileSystemHelpers.cpp", +] + +FINAL_LIBRARY = "xul-gtest" + +LOCAL_INCLUDES += [ + "/dom/fs/shared", + "/dom/fs/test/gtest", +] diff --git a/dom/fs/test/mochitest/head.js b/dom/fs/test/mochitest/head.js new file mode 100644 index 0000000000..321ca0291b --- /dev/null +++ b/dom/fs/test/mochitest/head.js @@ -0,0 +1,82 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +async function require_module(id) { + if (!require_module.moduleLoader) { + const { ModuleLoader } = await import( + "/tests/dom/quota/test/modules/ModuleLoader.js" + ); + + const base = window.location.href; + + const depth = "../../../../"; + + const { Assert } = await import("/tests/dom/quota/test/modules/Assert.js"); + + const { Utils } = await import("/tests/dom/quota/test/modules/Utils.js"); + + const proto = { + Assert, + Cr: SpecialPowers.Cr, + navigator, + TextEncoder, + Utils, + }; + + require_module.moduleLoader = new ModuleLoader(base, depth, proto); + } + + return require_module.moduleLoader.require(id); +} + +async function run_test_in_worker(script) { + const { runTestInWorker } = await import( + "/tests/dom/quota/test/modules/WorkerDriver.js" + ); + + const base = window.location.href; + + const listener = { + onOk(value, message) { + ok(value, message); + }, + onIs(a, b, message) { + is(a, b, message); + }, + onInfo(message) { + info(message); + }, + }; + + await runTestInWorker(script, base, listener); +} + +// XXX This can be removed once we use <profile>/storage. See bug 1798015. +async function removeAllEntries() { + const root = await navigator.storage.getDirectory(); + for await (const value of root.values()) { + root.removeEntry(value.name, { recursive: true }); + } +} + +add_setup(async function () { + const { setStoragePrefs, clearStoragesForOrigin } = await import( + "/tests/dom/quota/test/modules/StorageUtils.js" + ); + + const optionalPrefsToSet = [ + ["dom.fs.enabled", true], + ["dom.fs.writable_file_stream.enabled", true], + ["dom.workers.modules.enabled", true], + ]; + + await setStoragePrefs(optionalPrefsToSet); + + SimpleTest.registerCleanupFunction(async function () { + await removeAllEntries(); + + await clearStoragesForOrigin(SpecialPowers.wrap(document).nodePrincipal); + }); +}); diff --git a/dom/fs/test/mochitest/mochitest.ini b/dom/fs/test/mochitest/mochitest.ini new file mode 100644 index 0000000000..aa9f93b006 --- /dev/null +++ b/dom/fs/test/mochitest/mochitest.ini @@ -0,0 +1,24 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +skip-if = xorigin +support-files = + head.js + +# Skip all tests if xorigin since we'll fail GetStorage() with ePartitionForeignOrDeny +[test_basics.html] +scheme=https +[test_basics_worker.html] +scheme=https +[test_fileSystemDirectoryHandle.html] +scheme=https +[test_fileSystemDirectoryHandle_worker.html] +scheme=https +[test_syncAccessHandle_worker.html] +scheme=https +[test_writableFileStream.html] +scheme=https +[test_writableFileStream_worker.html] +scheme=https diff --git a/dom/fs/test/mochitest/test_basics.html b/dom/fs/test/mochitest/test_basics.html new file mode 100644 index 0000000000..5c609233b5 --- /dev/null +++ b/dom/fs/test/mochitest/test_basics.html @@ -0,0 +1,28 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function init() { + const testSet = "dom/fs/test/common/test_basics.js"; + + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + add_task(testItem); + }); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/test_basics_worker.html b/dom/fs/test/mochitest/test_basics_worker.html new file mode 100644 index 0000000000..72ad7b8c41 --- /dev/null +++ b/dom/fs/test/mochitest/test_basics_worker.html @@ -0,0 +1,22 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function worker() { + await run_test_in_worker("worker/test_basics_worker.js"); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/test_fileSystemDirectoryHandle.html b/dom/fs/test/mochitest/test_fileSystemDirectoryHandle.html new file mode 100644 index 0000000000..35f3a72e2f --- /dev/null +++ b/dom/fs/test/mochitest/test_fileSystemDirectoryHandle.html @@ -0,0 +1,28 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function init() { + const testSet = "dom/fs/test/common/test_fileSystemDirectoryHandle.js"; + + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + add_task(testItem); + }); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/test_fileSystemDirectoryHandle_worker.html b/dom/fs/test/mochitest/test_fileSystemDirectoryHandle_worker.html new file mode 100644 index 0000000000..2f52079435 --- /dev/null +++ b/dom/fs/test/mochitest/test_fileSystemDirectoryHandle_worker.html @@ -0,0 +1,22 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function worker() { + await run_test_in_worker("worker/test_fileSystemDirectoryHandle_worker.js"); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/test_syncAccessHandle_worker.html b/dom/fs/test/mochitest/test_syncAccessHandle_worker.html new file mode 100644 index 0000000000..42980b1d55 --- /dev/null +++ b/dom/fs/test/mochitest/test_syncAccessHandle_worker.html @@ -0,0 +1,22 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function worker() { + await run_test_in_worker("worker/test_syncAccessHandle_worker.js"); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/test_writableFileStream.html b/dom/fs/test/mochitest/test_writableFileStream.html new file mode 100644 index 0000000000..3d39349ef5 --- /dev/null +++ b/dom/fs/test/mochitest/test_writableFileStream.html @@ -0,0 +1,31 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function init() { + const testSet = "dom/fs/test/common/test_writableFileStream.js"; + + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + // We can't shrink storage size in a mochitest. + if (testItem.name != "quotaTest") { + add_task(testItem); + } + }); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/test_writableFileStream_worker.html b/dom/fs/test/mochitest/test_writableFileStream_worker.html new file mode 100644 index 0000000000..a762e78c3f --- /dev/null +++ b/dom/fs/test/mochitest/test_writableFileStream_worker.html @@ -0,0 +1,22 @@ +<!-- + Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ +--> +<html> +<head> + <title>File System Test</title> + + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + + <script type="text/javascript" src="head.js"></script> + <script type="text/javascript"> + add_task(async function worker() { + await run_test_in_worker("worker/test_writableFileStream_worker.js"); + }); + </script> +</head> + +<body></body> + +</html> diff --git a/dom/fs/test/mochitest/worker/.eslintrc.js b/dom/fs/test/mochitest/worker/.eslintrc.js new file mode 100644 index 0000000000..93bf938654 --- /dev/null +++ b/dom/fs/test/mochitest/worker/.eslintrc.js @@ -0,0 +1,12 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +module.exports = { + env: { + worker: true, + }, +}; diff --git a/dom/fs/test/mochitest/worker/dummy.js b/dom/fs/test/mochitest/worker/dummy.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/fs/test/mochitest/worker/dummy.js diff --git a/dom/fs/test/mochitest/worker/head.js b/dom/fs/test/mochitest/worker/head.js new file mode 100644 index 0000000000..72e1869bde --- /dev/null +++ b/dom/fs/test/mochitest/worker/head.js @@ -0,0 +1,22 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +async function require_module(id) { + if (!require_module.moduleLoader) { + importScripts("/tests/dom/quota/test/modules/worker/ModuleLoader.js"); + + const base = location.href; + + const depth = "../../../../../"; + + importScripts("/tests/dom/quota/test/modules/worker/Assert.js"); + + importScripts("/tests/dom/quota/test/modules/worker/Utils.js"); + + require_module.moduleLoader = new globalThis.ModuleLoader(base, depth); + } + + return require_module.moduleLoader.require(id); +} diff --git a/dom/fs/test/mochitest/worker/mochitest.ini b/dom/fs/test/mochitest/worker/mochitest.ini new file mode 100644 index 0000000000..9b5b2cbd98 --- /dev/null +++ b/dom/fs/test/mochitest/worker/mochitest.ini @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +support-files = + head.js + test_basics_worker.js + test_fileSystemDirectoryHandle_worker.js + test_syncAccessHandle_worker.js + test_writableFileStream_worker.js + +[dummy.js] +skip-if = true diff --git a/dom/fs/test/mochitest/worker/test_basics_worker.js b/dom/fs/test/mochitest/worker/test_basics_worker.js new file mode 100644 index 0000000000..e4a4958071 --- /dev/null +++ b/dom/fs/test/mochitest/worker/test_basics_worker.js @@ -0,0 +1,11 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module("dom/fs/test/common/test_basics.js"); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/mochitest/worker/test_fileSystemDirectoryHandle_worker.js b/dom/fs/test/mochitest/worker/test_fileSystemDirectoryHandle_worker.js new file mode 100644 index 0000000000..d4ba0b387c --- /dev/null +++ b/dom/fs/test/mochitest/worker/test_fileSystemDirectoryHandle_worker.js @@ -0,0 +1,13 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module( + "dom/fs/test/common/test_fileSystemDirectoryHandle.js" + ); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/mochitest/worker/test_syncAccessHandle_worker.js b/dom/fs/test/mochitest/worker/test_syncAccessHandle_worker.js new file mode 100644 index 0000000000..be53a5a7e1 --- /dev/null +++ b/dom/fs/test/mochitest/worker/test_syncAccessHandle_worker.js @@ -0,0 +1,16 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module( + "dom/fs/test/common/test_syncAccessHandle.js" + ); + Object.values(testCases).forEach(async testItem => { + // We can't shrink storage size in a mochitest. + if (testItem.name != "quotaTest") { + add_task(testItem); + } + }); +}); diff --git a/dom/fs/test/mochitest/worker/test_writableFileStream_worker.js b/dom/fs/test/mochitest/worker/test_writableFileStream_worker.js new file mode 100644 index 0000000000..f294a719db --- /dev/null +++ b/dom/fs/test/mochitest/worker/test_writableFileStream_worker.js @@ -0,0 +1,16 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module( + "dom/fs/test/common/test_writableFileStream.js" + ); + Object.values(testCases).forEach(async testItem => { + // We can't shrink storage size in a mochitest. + if (testItem.name != "quotaTest") { + add_task(testItem); + } + }); +}); diff --git a/dom/fs/test/moz.build b/dom/fs/test/moz.build new file mode 100644 index 0000000000..640712c670 --- /dev/null +++ b/dom/fs/test/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += [ + "common", + "gtest", + "xpcshell", +] + +MOCHITEST_MANIFESTS += [ + "mochitest/mochitest.ini", + "mochitest/worker/mochitest.ini", +] diff --git a/dom/fs/test/xpcshell/head.js b/dom/fs/test/xpcshell/head.js new file mode 100644 index 0000000000..4138e46ac9 --- /dev/null +++ b/dom/fs/test/xpcshell/head.js @@ -0,0 +1,100 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +async function require_module(id) { + if (!require_module.moduleLoader) { + const { ModuleLoader } = ChromeUtils.importESModule( + "resource://testing-common/dom/quota/test/modules/ModuleLoader.sys.mjs" + ); + + const base = Services.io.newFileURI(do_get_file("")).spec; + + const depth = "../../../../"; + + Cu.importGlobalProperties(["storage"]); + + const { Utils } = ChromeUtils.importESModule( + "resource://testing-common/dom/quota/test/modules/Utils.sys.mjs" + ); + + const proto = { + Assert, + Cr, + DOMException, + navigator: { + storage, + }, + TextEncoder, + Utils, + }; + + require_module.moduleLoader = new ModuleLoader(base, depth, proto); + } + + return require_module.moduleLoader.require(id); +} + +async function run_test_in_worker(script) { + const { runTestInWorker } = ChromeUtils.importESModule( + "resource://testing-common/dom/quota/test/modules/WorkerDriver.sys.mjs" + ); + + const base = "resource://testing-common/dom/fs/test/xpcshell/"; + + const listener = { + onOk(value, message) { + ok(value, message); + }, + onIs(a, b, message) { + Assert.equal(a, b, message); + }, + onInfo(message) { + info(message); + }, + }; + + await runTestInWorker(script, base, listener); +} + +add_setup(async function () { + const { + setStoragePrefs, + clearStoragePrefs, + clearStoragesForOrigin, + resetStorage, + } = ChromeUtils.importESModule( + "resource://testing-common/dom/quota/test/modules/StorageUtils.sys.mjs" + ); + + const optionalPrefsToSet = [ + ["dom.fs.enabled", true], + ["dom.fs.writable_file_stream.enabled", true], + ]; + + setStoragePrefs(optionalPrefsToSet); + + registerCleanupFunction(async function () { + const principal = Cc["@mozilla.org/systemprincipal;1"].createInstance( + Ci.nsIPrincipal + ); + + await clearStoragesForOrigin(principal); + + Services.prefs.clearUserPref( + "dom.quotaManager.temporaryStorage.fixedLimit" + ); + + await resetStorage(); + + const optionalPrefsToClear = [ + "dom.fs.enabled", + "dom.fs.writable_file_stream.enabled", + ]; + + clearStoragePrefs(optionalPrefsToClear); + }); + + do_get_profile(); +}); diff --git a/dom/fs/test/xpcshell/moz.build b/dom/fs/test/xpcshell/moz.build new file mode 100644 index 0000000000..108d89b0a9 --- /dev/null +++ b/dom/fs/test/xpcshell/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += [ + "worker", +] + +XPCSHELL_TESTS_MANIFESTS += [ + "xpcshell.ini", +] diff --git a/dom/fs/test/xpcshell/test_basics.js b/dom/fs/test/xpcshell/test_basics.js new file mode 100644 index 0000000000..9c70eaceb3 --- /dev/null +++ b/dom/fs/test/xpcshell/test_basics.js @@ -0,0 +1,14 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testSet = "dom/fs/test/common/test_basics.js"; + + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/test_basics_worker.js b/dom/fs/test/xpcshell/test_basics_worker.js new file mode 100644 index 0000000000..4569a6a88c --- /dev/null +++ b/dom/fs/test/xpcshell/test_basics_worker.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function worker() { + await run_test_in_worker("worker/test_basics_worker.js"); +}); diff --git a/dom/fs/test/xpcshell/test_fileSystemDirectoryHandle.js b/dom/fs/test/xpcshell/test_fileSystemDirectoryHandle.js new file mode 100644 index 0000000000..6349d9aab8 --- /dev/null +++ b/dom/fs/test/xpcshell/test_fileSystemDirectoryHandle.js @@ -0,0 +1,14 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testSet = "dom/fs/test/common/test_fileSystemDirectoryHandle.js"; + + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/test_fileSystemDirectoryHandle_worker.js b/dom/fs/test/xpcshell/test_fileSystemDirectoryHandle_worker.js new file mode 100644 index 0000000000..524b7352b4 --- /dev/null +++ b/dom/fs/test/xpcshell/test_fileSystemDirectoryHandle_worker.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function worker() { + await run_test_in_worker("worker/test_fileSystemDirectoryHandle_worker.js"); +}); diff --git a/dom/fs/test/xpcshell/test_syncAccessHandle_worker.js b/dom/fs/test/xpcshell/test_syncAccessHandle_worker.js new file mode 100644 index 0000000000..377efab598 --- /dev/null +++ b/dom/fs/test/xpcshell/test_syncAccessHandle_worker.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function worker() { + await run_test_in_worker("worker/test_syncAccessHandle_worker.js"); +}); diff --git a/dom/fs/test/xpcshell/test_writableFileStream.js b/dom/fs/test/xpcshell/test_writableFileStream.js new file mode 100644 index 0000000000..1ac9fdb793 --- /dev/null +++ b/dom/fs/test/xpcshell/test_writableFileStream.js @@ -0,0 +1,14 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testSet = "dom/fs/test/common/test_writableFileStream.js"; + + const testCases = await require_module(testSet); + + Object.values(testCases).forEach(testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/test_writableFileStream_worker.js b/dom/fs/test/xpcshell/test_writableFileStream_worker.js new file mode 100644 index 0000000000..879ca6c0ca --- /dev/null +++ b/dom/fs/test/xpcshell/test_writableFileStream_worker.js @@ -0,0 +1,8 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function worker() { + await run_test_in_worker("worker/test_writableFileStream_worker.js"); +}); diff --git a/dom/fs/test/xpcshell/worker/.eslintrc.js b/dom/fs/test/xpcshell/worker/.eslintrc.js new file mode 100644 index 0000000000..93bf938654 --- /dev/null +++ b/dom/fs/test/xpcshell/worker/.eslintrc.js @@ -0,0 +1,12 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +"use strict"; + +module.exports = { + env: { + worker: true, + }, +}; diff --git a/dom/fs/test/xpcshell/worker/dummy.js b/dom/fs/test/xpcshell/worker/dummy.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/dom/fs/test/xpcshell/worker/dummy.js diff --git a/dom/fs/test/xpcshell/worker/head.js b/dom/fs/test/xpcshell/worker/head.js new file mode 100644 index 0000000000..06a779841f --- /dev/null +++ b/dom/fs/test/xpcshell/worker/head.js @@ -0,0 +1,22 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +async function require_module(id) { + if (!require_module.moduleLoader) { + importScripts("/dom/quota/test/modules/worker/ModuleLoader.js"); + + const base = location.href; + + const depth = "../../../../../"; + + importScripts("/dom/quota/test/modules/worker/Assert.js"); + + importScripts("/dom/quota/test/modules/worker/Utils.js"); + + require_module.moduleLoader = new globalThis.ModuleLoader(base, depth); + } + + return require_module.moduleLoader.require(id); +} diff --git a/dom/fs/test/xpcshell/worker/moz.build b/dom/fs/test/xpcshell/worker/moz.build new file mode 100644 index 0000000000..c0230d1927 --- /dev/null +++ b/dom/fs/test/xpcshell/worker/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TESTING_JS_MODULES.dom.fs.test.xpcshell.worker += [ + "head.js", + "test_basics_worker.js", + "test_fileSystemDirectoryHandle_worker.js", + "test_syncAccessHandle_worker.js", + "test_writableFileStream_worker.js", +] diff --git a/dom/fs/test/xpcshell/worker/test_basics_worker.js b/dom/fs/test/xpcshell/worker/test_basics_worker.js new file mode 100644 index 0000000000..e4a4958071 --- /dev/null +++ b/dom/fs/test/xpcshell/worker/test_basics_worker.js @@ -0,0 +1,11 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module("dom/fs/test/common/test_basics.js"); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/worker/test_fileSystemDirectoryHandle_worker.js b/dom/fs/test/xpcshell/worker/test_fileSystemDirectoryHandle_worker.js new file mode 100644 index 0000000000..d4ba0b387c --- /dev/null +++ b/dom/fs/test/xpcshell/worker/test_fileSystemDirectoryHandle_worker.js @@ -0,0 +1,13 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module( + "dom/fs/test/common/test_fileSystemDirectoryHandle.js" + ); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/worker/test_syncAccessHandle_worker.js b/dom/fs/test/xpcshell/worker/test_syncAccessHandle_worker.js new file mode 100644 index 0000000000..e6c6a96143 --- /dev/null +++ b/dom/fs/test/xpcshell/worker/test_syncAccessHandle_worker.js @@ -0,0 +1,13 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module( + "dom/fs/test/common/test_syncAccessHandle.js" + ); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/worker/test_writableFileStream_worker.js b/dom/fs/test/xpcshell/worker/test_writableFileStream_worker.js new file mode 100644 index 0000000000..1e9bb12ae8 --- /dev/null +++ b/dom/fs/test/xpcshell/worker/test_writableFileStream_worker.js @@ -0,0 +1,13 @@ +/** + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +add_task(async function init() { + const testCases = await require_module( + "dom/fs/test/common/test_writableFileStream.js" + ); + Object.values(testCases).forEach(async testItem => { + add_task(testItem); + }); +}); diff --git a/dom/fs/test/xpcshell/xpcshell.ini b/dom/fs/test/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..f36a2eae8b --- /dev/null +++ b/dom/fs/test/xpcshell/xpcshell.ini @@ -0,0 +1,14 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +[DEFAULT] +head = head.js + +[test_basics.js] +[test_basics_worker.js] +[test_fileSystemDirectoryHandle.js] +[test_fileSystemDirectoryHandle_worker.js] +[test_syncAccessHandle_worker.js] +[test_writableFileStream.js] +[test_writableFileStream_worker.js] |