test_connection_online_backup.js (8238B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 /** 7 * This test file tests the backupToFileAsync function on 8 * mozIStorageAsyncConnection, which is implemented for mozStorageConnection. 9 * (but not implemented for mozilla::dom::cache::Connection). 10 */ 11 12 // The name of the backup database file that will be created. 13 const BACKUP_FILE_NAME = "test_storage.sqlite.backup"; 14 // The number of rows to insert into the test table in the source 15 // database. 16 const TEST_ROWS = 10; 17 // The page size to set on the source database. During setup, we assert that 18 // this does not match the default page size. 19 const TEST_PAGE_SIZE = 512; 20 21 /** 22 * This setup function creates a table inside of the test database and inserts 23 * some test rows. Critically, it keeps the connection to the database _open_ 24 * so that we can test the scenario where a database is copied with existing 25 * open connections. 26 * 27 * The database is closed in a cleanup function. 28 */ 29 add_setup(async () => { 30 let conn = await openAsyncDatabase(getTestDB()); 31 32 Assert.notEqual( 33 conn.defaultPageSize, 34 TEST_PAGE_SIZE, 35 "Should not default to having the TEST_PAGE_SIZE" 36 ); 37 38 await executeSimpleSQLAsync(conn, "PRAGMA page_size = " + TEST_PAGE_SIZE); 39 40 let createStmt = conn.createAsyncStatement("CREATE TABLE test(name TEXT)"); 41 await executeAsync(createStmt); 42 createStmt.finalize(); 43 44 registerCleanupFunction(async () => { 45 await asyncClose(conn); 46 }); 47 }); 48 49 /** 50 * Erases the test table and inserts TEST_ROWS rows into it. 51 * 52 * @param {mozIStorageAsyncConnection} connection 53 * The connection to use to prepare the database. 54 * @returns {Promise<undefined>} 55 */ 56 async function prepareSourceDatabase(connection) { 57 await executeSimpleSQLAsync(connection, "DELETE from test"); 58 for (let i = 0; i < TEST_ROWS; ++i) { 59 let name = `Database row #${i}`; 60 let stmt = connection.createAsyncStatement( 61 "INSERT INTO test (name) VALUES (:name)" 62 ); 63 stmt.params.name = name; 64 let result = await executeAsync(stmt); 65 stmt.finalize(); 66 Assert.ok(Components.isSuccessCode(result), `Inserted test row #${i}`); 67 } 68 } 69 70 /** 71 * Gets the test DB prepared with the testing table and rows. 72 * 73 * @returns {Promise<mozIStorageAsyncConnection>} 74 */ 75 async function getPreparedAsyncDatabase() { 76 let connection = await openAsyncDatabase(getTestDB()); 77 await prepareSourceDatabase(connection); 78 return connection; 79 } 80 81 /** 82 * Creates a copy of the database connected to via connection, and 83 * returns an nsIFile pointing at the created copy file once the 84 * copy is complete. 85 * 86 * @param {mozIStorageAsyncConnection} connection 87 * A connection to a database that should be copied. 88 * @param {number} [pagesPerStep] 89 * The number of pages to copy per step. If not supplied or is 0, falls back 90 * to the platform default which is currently 5. 91 * @param {number} [stepDelayMs] 92 * The number of milliseconds to wait between copying step. If not supplied 93 * or is 0, falls back to the platform default which is currently 250. 94 * @returns {Promise<nsIFile>} 95 */ 96 async function createCopy(connection, pagesPerStep, stepDelayMs) { 97 let destFilePath = PathUtils.join(PathUtils.profileDir, BACKUP_FILE_NAME); 98 let destFile = await IOUtils.getFile(destFilePath); 99 Assert.ok( 100 !(await IOUtils.exists(destFilePath)), 101 "Backup file shouldn't exist yet." 102 ); 103 104 await new Promise(resolve => { 105 connection.backupToFileAsync( 106 destFile, 107 result => { 108 Assert.ok(Components.isSuccessCode(result)); 109 resolve(result); 110 }, 111 pagesPerStep, 112 stepDelayMs 113 ); 114 }); 115 116 return destFile; 117 } 118 119 /** 120 * Opens up the database at file, asserts that the page_size matches 121 * TEST_PAGE_SIZE, and that the number of rows in the test table matches 122 * expectedEntries. Closes the connection after these assertions. 123 * 124 * @param {nsIFile} file 125 * The database file to be opened and queried. 126 * @param {number} [expectedEntries=TEST_ROWS] 127 * The expected number of rows in the test table. Defaults to TEST_ROWS. 128 * @returns {Promise<undefined>} 129 */ 130 async function assertSuccessfulCopy(file, expectedEntries = TEST_ROWS) { 131 let conn = await openAsyncDatabase(file); 132 133 await executeSimpleSQLAsync(conn, "PRAGMA page_size", resultSet => { 134 let result = resultSet.getNextRow(); 135 Assert.equal(TEST_PAGE_SIZE, result.getResultByIndex(0)); 136 }); 137 138 let stmt = conn.createAsyncStatement("SELECT COUNT(*) FROM test"); 139 let results = await new Promise(resolve => { 140 executeAsync(stmt, resolve); 141 }); 142 stmt.finalize(); 143 let row = results.getNextRow(); 144 let count = row.getResultByName("COUNT(*)"); 145 Assert.equal(count, expectedEntries, "Got the expected entries"); 146 147 Assert.ok( 148 !file.leafName.endsWith(".tmp"), 149 "Should not end in .tmp extension" 150 ); 151 152 await asyncClose(conn); 153 } 154 155 /** 156 * Test the basic behaviour of backupToFileAsync, and ensure that the copied 157 * database has the same characteristics and contents as the source database. 158 */ 159 add_task(async function test_backupToFileAsync() { 160 let newConnection = await getPreparedAsyncDatabase(); 161 let copyFile = await createCopy(newConnection); 162 Assert.ok( 163 await IOUtils.exists(copyFile.path), 164 "A new file was created by backupToFileAsync" 165 ); 166 167 await assertSuccessfulCopy(copyFile); 168 await IOUtils.remove(copyFile.path); 169 await asyncClose(newConnection); 170 }); 171 172 /** 173 * Tests that if insertions are underway during a copy, that those insertions 174 * show up in the copied database. 175 */ 176 add_task(async function test_backupToFileAsync_during_insert() { 177 let newConnection = await getPreparedAsyncDatabase(); 178 const NEW_ENTRIES = 5; 179 180 let copyFilePromise = createCopy(newConnection); 181 let inserts = []; 182 for (let i = 0; i < NEW_ENTRIES; ++i) { 183 let name = `New database row #${i}`; 184 let stmt = newConnection.createAsyncStatement( 185 "INSERT INTO test (name) VALUES (:name)" 186 ); 187 stmt.params.name = name; 188 inserts.push(executeAsync(stmt)); 189 stmt.finalize(); 190 } 191 await Promise.all(inserts); 192 let copyFile = await copyFilePromise; 193 194 Assert.ok( 195 await IOUtils.exists(copyFile.path), 196 "A new file was created by backupToFileAsync" 197 ); 198 199 await assertSuccessfulCopy(copyFile, TEST_ROWS + NEW_ENTRIES); 200 await IOUtils.remove(copyFile.path); 201 await asyncClose(newConnection); 202 }); 203 204 /** 205 * Tests that alternative pages-per-step and step delay values can be set when 206 * calling backupToFileAsync. 207 */ 208 add_task(async function test_backupToFileAsync_during_insert() { 209 let newConnection = await getPreparedAsyncDatabase(); 210 211 // Let's try some higher values... 212 let copyFile = await createCopy(newConnection, 15, 500); 213 Assert.ok( 214 await IOUtils.exists(copyFile.path), 215 "A new file was created by backupToFileAsync" 216 ); 217 218 await assertSuccessfulCopy(copyFile); 219 await IOUtils.remove(copyFile.path); 220 221 // And now we'll try some lower values... 222 copyFile = await createCopy(newConnection, 1, 25); 223 Assert.ok( 224 await IOUtils.exists(copyFile.path), 225 "A new file was created by backupToFileAsync" 226 ); 227 228 await assertSuccessfulCopy(copyFile); 229 await IOUtils.remove(copyFile.path); 230 231 await asyncClose(newConnection); 232 }); 233 234 /** 235 * Tests the behaviour of backupToFileAsync as exposed through Sqlite.sys.mjs. 236 */ 237 add_task(async function test_backupToFileAsync_via_Sqlite_module() { 238 let xpcomConnection = await getPreparedAsyncDatabase(); 239 let moduleConnection = await Sqlite.openConnection({ 240 path: xpcomConnection.databaseFile.path, 241 }); 242 243 let copyFilePath = PathUtils.join(PathUtils.profileDir, BACKUP_FILE_NAME); 244 await moduleConnection.backup(copyFilePath); 245 let copyFile = await IOUtils.getFile(copyFilePath); 246 Assert.ok(await IOUtils.exists(copyFilePath), "A new file was created"); 247 248 await assertSuccessfulCopy(copyFile); 249 await IOUtils.remove(copyFile.path); 250 251 // Also check that we can plumb through pagesPerStep and stepDelayMs. 252 await moduleConnection.backup(copyFilePath, 15, 500); 253 Assert.ok(await IOUtils.exists(copyFilePath), "A new file was created"); 254 await assertSuccessfulCopy(copyFile); 255 await IOUtils.remove(copyFile.path); 256 257 await moduleConnection.close(); 258 await asyncClose(xpcomConnection); 259 });