Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
*
* Any copyright is dedicated to the Public Domain.
*
* ***** END LICENSE BLOCK ***** */
/*
* Test suite for folder compaction
*
* Currently tested:
* - Compacting local folders
* TODO
* - Compacting imap offline stores.
*/
var { MailServices } = ChromeUtils.importESModule(
);
const { PromiseTestUtils } = ChromeUtils.importESModule(
);
Services.prefs.setCharPref(
"mail.serverDefaultStoreContractID",
"@mozilla.org/msgstore/berkeleystore;1"
);
// Globals
var gMsgFile1, gMsgFile2, gMsgFile3;
var gMsg2ID = "200804111417.m3BEHTk4030129@mrapp51.mozilla.org";
var gMsg3ID = "4849BF7B.2030800@example.com";
var gLocalFolder2;
var gLocalFolder3;
// After a compact (or other operation), this is what we expect the
// folder size to be.
var gExpectedFolderSize;
var gMsgHdrs = [];
var gExpectedInboxSize;
var gExpectedFolder2Size;
var gExpectedFolder3Size;
// Transfer message keys between function calls.
var gMsgKeys = [];
/** @implements {nsIMsgCopyServiceListener} */
var copyListenerWrap = {
setMessageKey(aKey) {
const hdr = localAccountUtils.inboxFolder.GetMessageHeader(aKey);
gMsgHdrs.push({ hdr, ID: hdr.messageId });
},
onStopCopy(aStatus) {
// Check: message successfully copied.
Assert.equal(aStatus, 0);
},
};
var urlListenerWrap = {
OnStopRunningUrl(aUrl, aExitCode) {
// Check: message successfully copied.
Assert.equal(aExitCode, 0);
if (gMsgKeys.length > 0) {
// Bug 854798: Check if the new message keys are the same as before compaction.
const folderMsgs = [...gMsgKeys.folder.messages];
// First message was deleted so skip it in the old array.
const expectedKeys = [...gMsgKeys].slice(1);
Assert.equal(folderMsgs.length, expectedKeys.length);
for (let i = 1; i < expectedKeys.length; i++) {
Assert.equal(folderMsgs[i], expectedKeys[i]);
}
gMsgKeys.length = 0;
}
},
};
function copyFileMessage(file, destFolder, isDraftOrTemplate) {
const listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
MailServices.copy.copyFileMessage(
file,
destFolder,
null,
isDraftOrTemplate,
0,
"",
listener,
null
);
return listener.promise;
}
function copyMessages(items, isMove, srcFolder, destFolder) {
const listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
MailServices.copy.copyMessages(
srcFolder,
items,
destFolder,
isMove,
listener,
null,
true
);
return listener.promise;
}
function deleteMessages(srcFolder, items) {
const listener = new PromiseTestUtils.PromiseCopyListener(copyListenerWrap);
srcFolder.deleteMessages(items, null, false, true, listener, true);
return listener.promise;
}
function verifyMsgOffsets(folder) {
const msgDB = folder.msgDatabase;
const enumerator = msgDB.enumerateMessages();
if (enumerator) {
for (const header of enumerator) {
if (header instanceof Ci.nsIMsgDBHdr) {
const storeToken = header.getStringProperty("storeToken");
Assert.equal(storeToken, header.messageOffset);
}
}
}
}
/**
* Calculate the expected size of a (compacted) mbox, based on
* msgDB entries. For use later in verifyMboxSize().
* We're assuming bare "From " lines, which _won't_ be right, but
* verifyMboxSize() will do the same, so it works out.
*/
function calculateExpectedMboxSize(folder) {
const msgDB = folder.msgDatabase;
let totalSize = 0;
for (const header of msgDB.enumerateMessages()) {
totalSize += "From \r\n".length; // Pared-down mbox separator.
totalSize += header.messageSize; // The actual message data.
totalSize += "\r\n".length; // Blank line between messages.
}
return totalSize;
}
/**
* Make sure the mbox size of folder matches expectedSize.
* We can't just use the mbox file size, since we need to normalise the
* "From " separator lines, which can be variable length.
* So we load in the whole file, strip anything on lines after the "From ",
* and check the length after that.
*/
async function verifyMboxSize(folder, expectedSize) {
showMessages(folder, "verifyMboxSize");
let mbox = await IOUtils.readUTF8(folder.filePath.path);
// Pared-down mbox separator.
mbox = mbox.replace(/^From .*$/gm, "From ");
Assert.equal(mbox.length, expectedSize);
}
/**
* Debug utility to show the key/offset/ID relationship of messages in a folder.
* Disabled but not removed, as it's just so useful for troubleshooting
* if anything goes wrong!
*/
function showMessages() {
/*
dump(`***** Show messages for folder <${folder.name}> "${text} *****\n`);
for (const hdr of folder.messages) {
const storeToken = hdr.getStringProperty("storeToken");
dump(
` key: ${hdr.messageKey} storeToken: ${storeToken} offset: ${hdr.messageOffset} size: ${hdr.messageSize} ID: ${hdr.messageId}\n`
);
}
*/
}
/*
* TESTS
*/
// Beware before commenting out a test -- later tests might just depend on earlier ones
var gTestArray = [
// Copying messages from files
async function testCopyFileMessage1() {
await copyFileMessage(gMsgFile1, localAccountUtils.inboxFolder, false);
},
async function testCopyFileMessage2() {
await copyFileMessage(gMsgFile2, localAccountUtils.inboxFolder, false);
},
async function testCopyFileMessage3() {
await copyFileMessage(gMsgFile3, localAccountUtils.inboxFolder, true);
showMessages(
localAccountUtils.inboxFolder,
"after initial 3 messages copy to inbox"
);
},
// Moving/copying messages
async function testCopyMessages1() {
await copyMessages(
[gMsgHdrs[0].hdr],
false,
localAccountUtils.inboxFolder,
gLocalFolder2
);
},
async function testCopyMessages2() {
await copyMessages(
[gMsgHdrs[1].hdr, gMsgHdrs[2].hdr],
false,
localAccountUtils.inboxFolder,
gLocalFolder2
);
showMessages(gLocalFolder2, "after copying 3 messages");
},
async function testMoveMessages1() {
await copyMessages(
[gMsgHdrs[0].hdr, gMsgHdrs[1].hdr],
true,
localAccountUtils.inboxFolder,
gLocalFolder3
);
showMessages(localAccountUtils.inboxFolder, "after moving 2 messages");
showMessages(gLocalFolder3, "after moving 2 messages");
},
// Deleting messages
async function testDeleteMessages1() {
// delete to trash
// Let's take a moment to re-initialize stuff that got moved
var folder3DB = gLocalFolder3.msgDatabase;
gMsgHdrs[0].hdr = folder3DB.getMsgHdrForMessageID(gMsgHdrs[0].ID);
// Store message keys before deletion and compaction.
gMsgKeys.folder = gLocalFolder3;
for (const header of gLocalFolder3.messages) {
gMsgKeys.push(header.messageKey);
}
// Now delete the message
await deleteMessages(gLocalFolder3, [gMsgHdrs[0].hdr]);
showMessages(gLocalFolder3, "after deleting 1 message to trash");
},
async function compactFolder() {
gExpectedFolderSize = calculateExpectedMboxSize(gLocalFolder3);
Assert.notEqual(gLocalFolder3.expungedBytes, 0);
const listener = new PromiseTestUtils.PromiseUrlListener(urlListenerWrap);
gLocalFolder3.compact(listener, null);
await listener.promise;
showMessages(gLocalFolder3, "after compact");
},
async function testDeleteMessages2() {
await verifyMboxSize(gLocalFolder3, gExpectedFolderSize);
verifyMsgOffsets(gLocalFolder3);
var folder2DB = gLocalFolder2.msgDatabase;
gMsgHdrs[0].hdr = folder2DB.getMsgHdrForMessageID(gMsgHdrs[0].ID);
// Store message keys before deletion and compaction.
gMsgKeys.folder = gLocalFolder2;
for (const header of gLocalFolder2.messages) {
gMsgKeys.push(header.messageKey);
}
// Now delete the message
await deleteMessages(gLocalFolder2, [gMsgHdrs[0].hdr]);
showMessages(gLocalFolder2, "after deleting 1 message");
},
async function compactAllFolders() {
gExpectedInboxSize = calculateExpectedMboxSize(
localAccountUtils.inboxFolder
);
gExpectedFolder2Size = calculateExpectedMboxSize(gLocalFolder2);
gExpectedFolder3Size = calculateExpectedMboxSize(gLocalFolder3);
// Save the first message key, which will change after compact with
// rebuild.
const f2m2Key =
gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg2ID).messageKey;
// force expunged bytes count to get cached.
gLocalFolder2.expungedBytes;
// mark localFolder2 as having an invalid db, and remove it
// for good measure.
gLocalFolder2.msgDatabase.summaryValid = false;
gLocalFolder2.msgDatabase = null;
gLocalFolder2.ForceDBClosed();
const dbPath = gLocalFolder2.filePath;
dbPath.leafName = dbPath.leafName + ".msf";
dbPath.remove(false);
showMessages(localAccountUtils.inboxFolder, "before compactAll");
// Save the key for the inbox message, we'll check after compact that it
// did not change.
const preInboxMsg3Key =
localAccountUtils.inboxFolder.msgDatabase.getMsgHdrForMessageID(
gMsg3ID
).messageKey;
// We used to check here that the keys did not change during rebuild.
// But that is no true in general, it was only conicidental since the
// checked folder had never been compacted, so the key equaled the offset.
// We do not in guarantee that, indeed after rebuild we expect the keys
// to change.
const checkResult = {
OnStopRunningUrl(aUrl, aExitCode) {
// Check: message successfully compacted.
Assert.equal(aExitCode, 0);
},
};
const listener = new PromiseTestUtils.PromiseUrlListener(checkResult);
localAccountUtils.inboxFolder.compactAll(listener, null);
await listener.promise;
showMessages(localAccountUtils.inboxFolder, "after compactAll");
showMessages(gLocalFolder2, "after compactAll");
// For the inbox, which was compacted but not rebuild, key is unchanged.
const postInboxMsg3Key =
localAccountUtils.inboxFolder.msgDatabase.getMsgHdrForMessageID(
gMsg3ID
).messageKey;
Assert.equal(preInboxMsg3Key, postInboxMsg3Key);
// For folder2, which was rebuilt, keys change but all messages should exist.
const message2 = gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg2ID);
Assert.ok(message2);
Assert.ok(gLocalFolder2.msgDatabase.getMsgHdrForMessageID(gMsg3ID));
// In folder2, gMsg2ID is the first message. After compact with database
// rebuild, that key has now changed.
Assert.notEqual(message2.messageKey, f2m2Key);
},
async function lastTestCheck() {
await verifyMboxSize(localAccountUtils.inboxFolder, gExpectedInboxSize);
await verifyMboxSize(gLocalFolder2, gExpectedFolder2Size);
await verifyMboxSize(gLocalFolder3, gExpectedFolder3Size);
verifyMsgOffsets(gLocalFolder2);
verifyMsgOffsets(gLocalFolder3);
verifyMsgOffsets(localAccountUtils.inboxFolder);
},
];
function run_test() {
localAccountUtils.loadLocalMailAccount();
// Load up some messages so that we can copy them in later.
gMsgFile1 = do_get_file("../../../data/bugmail10");
gMsgFile2 = do_get_file("../../../data/bugmail11");
gMsgFile3 = do_get_file("../../../data/draft1");
// Create another folder to move and copy messages around, and force initialization.
gLocalFolder2 = localAccountUtils.rootFolder.createLocalSubfolder("folder2");
// Create a third folder for more testing.
gLocalFolder3 = localAccountUtils.rootFolder.createLocalSubfolder("folder3");
gTestArray.forEach(x => add_task(x));
run_next_test();
}