Bug 888784 - Remove FormHistory.getSchemaVersion and update some tests that used it to use add_task. r=mak

MozReview-Commit-ID: EQBksMqeNm2

--HG--
extra : rebase_source : 68892eb791b2001532f2089f1f7fb277517a5027
This commit is contained in:
Mike Conley 2017-11-30 17:00:07 -05:00
Родитель 9870ba6719
Коммит b2dbfe8731
8 изменённых файлов: 159 добавлений и 259 удалений

Просмотреть файл

@ -1415,10 +1415,6 @@ this.FormHistory = {
return pending;
},
get schemaVersion() {
return dbConnection.schemaVersion;
},
// This is used only so that the test can verify deleted table support.
get _supportsDeletedTable() {
return supportsDeletedTable;

Просмотреть файл

@ -34,6 +34,15 @@ function getDBVersion(dbfile) {
return version;
}
function getFormHistoryDBVersion() {
let profileDir = do_get_profile();
// Cleanup from any previous tests or failures.
let dbFile = profileDir.clone();
dbFile.append("formhistory.sqlite");
return getDBVersion(dbFile);
}
const isGUID = /[A-Za-z0-9\+\/]{16}/;
// Find form history entries.
@ -102,9 +111,9 @@ function addEntry(name, value, then) {
}, then);
}
function promiseCountEntries(name, value) {
return new Promise(res => {
countEntries(name, value, res);
function promiseCountEntries(name, value, checkFn = () => {}) {
return new Promise(resolve => {
countEntries(name, value, function(result) { checkFn(result); resolve(result); });
});
}
@ -134,6 +143,23 @@ function updateFormHistory(changes, then) {
});
}
function promiseUpdate(change) {
return new Promise((resolve, reject) => {
FormHistory.update(change, {
handleError(error) {
this._error = error;
},
handleCompletion(reason) {
if (reason) {
reject(this._error);
} else {
resolve();
}
},
});
});
}
/**
* Logs info to the console in the standard way (includes the filename).
*

Просмотреть файл

@ -2,59 +2,29 @@
* 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/. */
var dbFile;
XPCOMUtils.defineLazyModuleGetter(this, "TestUtils",
"resource://testing-common/TestUtils.jsm");
function promiseExpiration() {
let promise = TestUtils.topicObserved("satchel-storage-changed", (subject, data) => {
return data == "formhistory-expireoldentries";
});
function triggerExpiration() {
// We can't easily fake a "daily idle" event, so for testing purposes form
// history listens for another notification to trigger an immediate
// expiration.
Services.obs.notifyObservers(null, "formhistory-expire-now");
return promise;
}
var checkExists = function(num) { Assert.ok(num > 0); next_test(); };
var checkNotExists = function(num) { Assert.ok(!num); next_test(); };
var TestObserver = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
observe(subject, topic, data) {
Assert.equal(topic, "satchel-storage-changed");
if (data == "formhistory-expireoldentries") {
next_test();
}
},
};
function test_finished() {
// Make sure we always reset prefs.
if (Services.prefs.prefHasUserValue("browser.formfill.expire_days")) {
Services.prefs.clearUserPref("browser.formfill.expire_days");
}
do_test_finished();
}
var iter = tests();
function run_test() {
do_test_pending();
iter.next();
}
function next_test() {
iter.next();
}
function* tests() {
Services.obs.addObserver(TestObserver, "satchel-storage-changed", true);
add_task(async function() {
// ===== test init =====
let testfile = do_get_file("asyncformhistory_expire.sqlite");
let profileDir = do_get_profile();
// Cleanup from any previous tests or failures.
dbFile = profileDir.clone();
let dbFile = profileDir.clone();
dbFile.append("formhistory.sqlite");
if (dbFile.exists()) {
dbFile.remove(false);
@ -67,99 +37,94 @@ function* tests() {
Assert.ok(!Services.prefs.prefHasUserValue("browser.formfill.expire_days"));
// Sanity check initial state
yield countEntries(null, null, function(num) { Assert.equal(508, num); next_test(); });
yield countEntries("name-A", "value-A", checkExists); // lastUsed == distant past
yield countEntries("name-B", "value-B", checkExists); // lastUsed == distant future
Assert.equal(508, await promiseCountEntries(null, null));
Assert.ok(await promiseCountEntries("name-A", "value-A") > 0); // lastUsed == distant past
Assert.ok(await promiseCountEntries("name-B", "value-B") > 0); // lastUsed == distant future
Assert.equal(CURRENT_SCHEMA, FormHistory.schemaVersion);
Assert.equal(CURRENT_SCHEMA, getDBVersion(dbFile));
// Add a new entry
yield countEntries("name-C", "value-C", checkNotExists);
yield addEntry("name-C", "value-C", next_test);
yield countEntries("name-C", "value-C", checkExists);
Assert.equal(0, await promiseCountEntries("name-C", "value-C"));
await promiseAddEntry("name-C", "value-C");
Assert.equal(1, await promiseCountEntries("name-C", "value-C"));
// Update some existing entries to have ages relative to when the test runs.
let now = 1000 * Date.now();
let updateLastUsed = function updateLastUsedFn(results, age) {
let updateLastUsed = (results, age) => {
let lastUsed = now - age * 24 * PR_HOURS;
let changes = [];
for (let r = 0; r < results.length; r++) {
changes.push({ op: "update", lastUsed, guid: results[r].guid });
for (let result of results) {
changes.push({ op: "update", lastUsed, guid: result.guid });
}
return changes;
};
let results = yield searchEntries(["guid"], { lastUsed: 181 }, iter);
yield updateFormHistory(updateLastUsed(results, 181), next_test);
let results = await FormHistory.search(["guid"], { lastUsed: 181 });
await promiseUpdate(updateLastUsed(results, 181));
results = yield searchEntries(["guid"], { lastUsed: 179 }, iter);
yield updateFormHistory(updateLastUsed(results, 179), next_test);
results = await FormHistory.search(["guid"], { lastUsed: 179 });
await promiseUpdate(updateLastUsed(results, 179));
results = yield searchEntries(["guid"], { lastUsed: 31 }, iter);
yield updateFormHistory(updateLastUsed(results, 31), next_test);
results = await FormHistory.search(["guid"], { lastUsed: 31 });
await promiseUpdate(updateLastUsed(results, 31));
results = yield searchEntries(["guid"], { lastUsed: 29 }, iter);
yield updateFormHistory(updateLastUsed(results, 29), next_test);
results = await FormHistory.search(["guid"], { lastUsed: 29 });
await promiseUpdate(updateLastUsed(results, 29));
results = yield searchEntries(["guid"], { lastUsed: 9999 }, iter);
yield updateFormHistory(updateLastUsed(results, 11), next_test);
results = await FormHistory.search(["guid"], { lastUsed: 9999 });
await promiseUpdate(updateLastUsed(results, 11));
results = yield searchEntries(["guid"], { lastUsed: 9 }, iter);
yield updateFormHistory(updateLastUsed(results, 9), next_test);
results = await FormHistory.search(["guid"], { lastUsed: 9 });
await promiseUpdate(updateLastUsed(results, 9));
yield countEntries("name-A", "value-A", checkExists);
yield countEntries("181DaysOld", "foo", checkExists);
yield countEntries("179DaysOld", "foo", checkExists);
yield countEntries(null, null, function(num) { Assert.equal(509, num); next_test(); });
Assert.ok(await promiseCountEntries("name-A", "value-A") > 0);
Assert.ok(await promiseCountEntries("181DaysOld", "foo") > 0);
Assert.ok(await promiseCountEntries("179DaysOld", "foo") > 0);
Assert.equal(509, await promiseCountEntries(null, null));
// 2 entries are expected to expire.
triggerExpiration();
yield;
await promiseExpiration();
yield countEntries("name-A", "value-A", checkNotExists);
yield countEntries("181DaysOld", "foo", checkNotExists);
yield countEntries("179DaysOld", "foo", checkExists);
yield countEntries(null, null, function(num) { Assert.equal(507, num); next_test(); });
Assert.equal(0, await promiseCountEntries("name-A", "value-A"));
Assert.equal(0, await promiseCountEntries("181DaysOld", "foo"));
Assert.ok(await promiseCountEntries("179DaysOld", "foo") > 0);
Assert.equal(507, await promiseCountEntries(null, null));
// And again. No change expected.
triggerExpiration();
yield;
await promiseExpiration();
yield countEntries(null, null, function(num) { Assert.equal(507, num); next_test(); });
Assert.equal(507, await promiseCountEntries(null, null));
// Set formfill pref to 30 days.
Services.prefs.setIntPref("browser.formfill.expire_days", 30);
yield countEntries("179DaysOld", "foo", checkExists);
yield countEntries("bar", "31days", checkExists);
yield countEntries("bar", "29days", checkExists);
yield countEntries(null, null, function(num) { Assert.equal(507, num); next_test(); });
triggerExpiration();
yield;
Assert.ok(await promiseCountEntries("179DaysOld", "foo") > 0);
Assert.ok(await promiseCountEntries("bar", "31days") > 0);
Assert.ok(await promiseCountEntries("bar", "29days") > 0);
Assert.equal(507, await promiseCountEntries(null, null));
yield countEntries("179DaysOld", "foo", checkNotExists);
yield countEntries("bar", "31days", checkNotExists);
yield countEntries("bar", "29days", checkExists);
yield countEntries(null, null, function(num) { Assert.equal(505, num); next_test(); });
await promiseExpiration();
Assert.equal(0, await promiseCountEntries("179DaysOld", "foo"));
Assert.equal(0, await promiseCountEntries("bar", "31days"));
Assert.ok(await promiseCountEntries("bar", "29days") > 0);
Assert.equal(505, await promiseCountEntries(null, null));
// Set override pref to 10 days and expire. This expires a large batch of
// entries, and should trigger a VACCUM to reduce file size.
Services.prefs.setIntPref("browser.formfill.expire_days", 10);
yield countEntries("bar", "29days", checkExists);
yield countEntries("9DaysOld", "foo", checkExists);
yield countEntries(null, null, function(num) { Assert.equal(505, num); next_test(); });
Assert.ok(await promiseCountEntries("bar", "29days") > 0);
Assert.ok(await promiseCountEntries("9DaysOld", "foo") > 0);
Assert.equal(505, await promiseCountEntries(null, null));
triggerExpiration();
yield;
await promiseExpiration();
yield countEntries("bar", "29days", checkNotExists);
yield countEntries("9DaysOld", "foo", checkExists);
yield countEntries("name-B", "value-B", checkExists);
yield countEntries("name-C", "value-C", checkExists);
yield countEntries(null, null, function(num) { Assert.equal(3, num); next_test(); });
test_finished();
}
Assert.equal(0, await promiseCountEntries("bar", "29days"));
Assert.ok(await promiseCountEntries("9DaysOld", "foo") > 0);
Assert.ok(await promiseCountEntries("name-B", "value-B") > 0);
Assert.ok(await promiseCountEntries("name-C", "value-C") > 0);
Assert.equal(3, await promiseCountEntries(null, null));
});

Просмотреть файл

@ -2,19 +2,11 @@
* 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/. */
var testnum = 0;
add_task(async function() {
let testnum = 0;
var iter;
function run_test() {
do_test_pending();
iter = next_test();
iter.next();
}
function* next_test() {
try {
// ===== test init =====
// ===== test init =====
let testfile = do_get_file("formhistory_v3.sqlite");
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
@ -28,6 +20,8 @@ function* next_test() {
testfile.copyTo(profileDir, "formhistory.sqlite");
Assert.equal(3, getDBVersion(testfile));
Assert.ok(destFile.exists());
// ===== 1 =====
testnum++;
@ -35,23 +29,24 @@ function* next_test() {
destFile.append("formhistory.sqlite");
let dbConnection = Services.storage.openUnsharedDatabase(destFile);
// Do something that will cause FormHistory to access and upgrade the
// database
await FormHistory.count({});
// check for upgraded schema.
Assert.equal(CURRENT_SCHEMA, FormHistory.schemaVersion);
Assert.equal(CURRENT_SCHEMA, getDBVersion(destFile));
// Check that the index was added
Assert.ok(dbConnection.tableExists("moz_deleted_formhistory"));
dbConnection.close();
// check for upgraded schema.
Assert.equal(CURRENT_SCHEMA, FormHistory.schemaVersion);
Assert.equal(CURRENT_SCHEMA, getDBVersion(destFile));
// check that an entry still exists
yield countEntries("name-A", "value-A",
function(num) {
Assert.ok(num > 0);
do_test_finished();
}
);
let num = await promiseCountEntries("name-A", "value-A");
Assert.ok(num > 0);
} catch (e) {
throw new Error(`FAILED in test #${testnum} -- ${e}`);
}
}
});

Просмотреть файл

@ -2,19 +2,11 @@
* 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/. */
var testnum = 0;
add_task(async function() {
let testnum = 0;
var iter;
function run_test() {
do_test_pending();
iter = next_test();
iter.next();
}
function* next_test() {
try {
// ===== test init =====
// ===== test init =====
let testfile = do_get_file("formhistory_v3v4.sqlite");
let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
@ -35,21 +27,20 @@ function* next_test() {
destFile.append("formhistory.sqlite");
let dbConnection = Services.storage.openUnsharedDatabase(destFile);
// Do something that will cause FormHistory to access and upgrade the
// database
await FormHistory.count({});
// check for upgraded schema.
Assert.equal(CURRENT_SCHEMA, FormHistory.schemaVersion);
Assert.equal(CURRENT_SCHEMA, getDBVersion(destFile));
// Check that the index was added
Assert.ok(dbConnection.tableExists("moz_deleted_formhistory"));
dbConnection.close();
// check that an entry still exists
yield countEntries("name-A", "value-A",
function(num) {
Assert.ok(num > 0);
do_test_finished();
}
);
Assert.ok(await promiseCountEntries("name-A", "value-A") > 0);
} catch (e) {
throw new Error(`FAILED in test #${testnum} -- ${e}`);
}
}
});

Просмотреть файл

@ -11,18 +11,7 @@
* Part B tests this when the columns do *not* match, so the DB is reset.
*/
var iter = tests();
function run_test() {
do_test_pending();
iter.next();
}
function next_test() {
iter.next();
}
function* tests() {
add_task(async function() {
let testnum = 0;
try {
@ -40,33 +29,28 @@ function* tests() {
testfile.copyTo(profileDir, "formhistory.sqlite");
Assert.equal(999, getDBVersion(testfile));
let checkZero = function(num) { Assert.equal(num, 0); next_test(); };
let checkOne = function(num) { Assert.equal(num, 1); next_test(); };
// ===== 1 =====
testnum++;
// Check for expected contents.
yield countEntries(null, null, function(num) { Assert.ok(num > 0); next_test(); });
yield countEntries("name-A", "value-A", checkOne);
yield countEntries("name-B", "value-B", checkOne);
yield countEntries("name-C", "value-C1", checkOne);
yield countEntries("name-C", "value-C2", checkOne);
yield countEntries("name-E", "value-E", checkOne);
Assert.ok(await promiseCountEntries(null, null) > 0);
Assert.equal(1, await promiseCountEntries("name-A", "value-A"));
Assert.equal(1, await promiseCountEntries("name-B", "value-B"));
Assert.equal(1, await promiseCountEntries("name-C", "value-C1"));
Assert.equal(1, await promiseCountEntries("name-C", "value-C2"));
Assert.equal(1, await promiseCountEntries("name-E", "value-E"));
// check for downgraded schema.
Assert.equal(CURRENT_SCHEMA, FormHistory.schemaVersion);
Assert.equal(CURRENT_SCHEMA, getDBVersion(destFile));
// ===== 2 =====
testnum++;
// Exercise adding and removing a name/value pair
yield countEntries("name-D", "value-D", checkZero);
yield updateEntry("add", "name-D", "value-D", next_test);
yield countEntries("name-D", "value-D", checkOne);
yield updateEntry("remove", "name-D", "value-D", next_test);
yield countEntries("name-D", "value-D", checkZero);
Assert.equal(0, await promiseCountEntries("name-D", "value-D"));
await promiseUpdateEntry("add", "name-D", "value-D");
Assert.equal(1, await promiseCountEntries("name-D", "value-D"));
await promiseUpdateEntry("remove", "name-D", "value-D");
Assert.equal(0, await promiseCountEntries("name-D", "value-D"));
} catch (e) {
throw new Error(`FAILED in test #${testnum} -- ${e}`);
}
do_test_finished();
}
});

Просмотреть файл

@ -11,18 +11,7 @@
* Part B tests this when the columns do *not* match, so the DB is reset.
*/
var iter = tests();
function run_test() {
do_test_pending();
iter.next();
}
function next_test() {
iter.next();
}
function* tests() {
add_task(async function() {
let testnum = 0;
try {
@ -44,10 +33,7 @@ function* tests() {
}
testfile.copyTo(profileDir, "formhistory.sqlite");
Assert.equal(999, getDBVersion(testfile));
let checkZero = function(num) { Assert.equal(num, 0); next_test(); };
let checkOne = function(num) { Assert.equal(num, 1); next_test(); };
Assert.equal(999, getDBVersion(destFile));
// ===== 1 =====
testnum++;
@ -56,7 +42,7 @@ function* tests() {
// DB init is done lazily so the DB shouldn't be created yet.
Assert.ok(!bakFile.exists());
// Doing any request to the DB should create it.
yield countEntries("", "", next_test);
await promiseCountEntries("", "");
Assert.ok(bakFile.exists());
bakFile.remove(false);
@ -64,27 +50,25 @@ function* tests() {
// ===== 2 =====
testnum++;
// File should be empty
yield countEntries(null, null, function(num) { Assert.ok(!num); next_test(); });
yield countEntries("name-A", "value-A", checkZero);
Assert.ok(!await promiseCountEntries(null, null));
Assert.equal(0, await promiseCountEntries("name-A", "value-A"));
// check for current schema.
Assert.equal(CURRENT_SCHEMA, FormHistory.schemaVersion);
Assert.equal(CURRENT_SCHEMA, getDBVersion(destFile));
// ===== 3 =====
testnum++;
// Try adding an entry
yield updateEntry("add", "name-A", "value-A", next_test);
yield countEntries(null, null, checkOne);
yield countEntries("name-A", "value-A", checkOne);
await promiseUpdateEntry("add", "name-A", "value-A");
Assert.equal(1, await promiseCountEntries(null, null));
Assert.equal(1, await promiseCountEntries("name-A", "value-A"));
// ===== 4 =====
testnum++;
// Try removing an entry
yield updateEntry("remove", "name-A", "value-A", next_test);
yield countEntries(null, null, checkZero);
yield countEntries("name-A", "value-A", checkZero);
await promiseUpdateEntry("remove", "name-A", "value-A");
Assert.equal(0, await promiseCountEntries(null, null));
Assert.equal(0, await promiseCountEntries("name-A", "value-A"));
} catch (e) {
throw new Error(`FAILED in test #${testnum} -- ${e}`);
}
do_test_finished();
}
});

Просмотреть файл

@ -60,47 +60,6 @@ function promiseUpdateEntry(op, name, value) {
return promiseUpdate(change);
}
function promiseUpdate(change) {
return new Promise((resolve, reject) => {
FormHistory.update(change, {
handleError(error) {
this._error = error;
},
handleCompletion(reason) {
if (reason) {
reject(this._error);
} else {
resolve();
}
},
});
});
}
function promiseSearchEntries(terms, params) {
return new Promise((resolve, reject) => {
let results = [];
FormHistory.search(terms, params,
{ handleResult: result => results.push(result),
handleError(error) {
do_throw("Error occurred searching form history: " + error);
reject(error);
},
handleCompletion(reason) {
if (!reason) {
resolve(results);
}
},
});
});
}
function promiseCountEntries(name, value, checkFn) {
return new Promise(resolve => {
countEntries(name, value, function(result) { checkFn(result); resolve(); });
});
}
add_task(async function() {
let oldSupportsDeletedTable = FormHistory._supportsDeletedTable;
FormHistory._supportsDeletedTable = true;
@ -275,8 +234,8 @@ add_task(async function() {
return undefined;
};
let results = await promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field1", value: "value1" });
let results = await FormHistory.search(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field1", value: "value1" });
let [timesUsed, firstUsed, lastUsed] = processFirstResult(results);
Assert.equal(1, timesUsed);
Assert.ok(firstUsed > 0);
@ -295,7 +254,7 @@ add_task(async function() {
// Update a single entry
testnum++;
results = await promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1" });
results = await FormHistory.search(["guid"], { fieldname: "field1", value: "value1" });
let guid = processFirstResult(results)[3];
await promiseUpdate({ op: "update", guid, value: "modifiedValue" });
@ -310,8 +269,8 @@ add_task(async function() {
await promiseUpdate({ op: "add", fieldname: "field2", value: "value2",
timesUsed: 20, firstUsed: 100, lastUsed: 500 });
results = await promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field2", value: "value2" });
results = await FormHistory.search(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field2", value: "value2" });
[timesUsed, firstUsed, lastUsed] = processFirstResult(results);
Assert.equal(20, timesUsed);
@ -324,8 +283,8 @@ add_task(async function() {
testnum++;
await promiseUpdate({ op: "bump", fieldname: "field2", value: "value2",
timesUsed: 20, firstUsed: 100, lastUsed: 500 });
results = await promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field2", value: "value2" });
results = await FormHistory.search(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field2", value: "value2" });
[timesUsed, firstUsed, lastUsed] = processFirstResult(results);
Assert.equal(21, timesUsed);
Assert.equal(100, firstUsed);
@ -337,8 +296,8 @@ add_task(async function() {
testnum++;
await promiseUpdate({ op: "bump", fieldname: "field3", value: "value3",
timesUsed: 10, firstUsed: 50, lastUsed: 400 });
results = await promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field3", value: "value3" });
results = await FormHistory.search(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field3", value: "value3" });
[timesUsed, firstUsed, lastUsed] = processFirstResult(results);
Assert.equal(10, timesUsed);
Assert.equal(50, firstUsed);
@ -348,11 +307,11 @@ add_task(async function() {
// ===== 16 =====
// Bump an entry with a guid
testnum++;
results = await promiseSearchEntries(["guid"], { fieldname: "field3", value: "value3" });
results = await FormHistory.search(["guid"], { fieldname: "field3", value: "value3" });
guid = processFirstResult(results)[3];
await promiseUpdate({ op: "bump", guid, timesUsed: 20, firstUsed: 55, lastUsed: 400 });
results = await promiseSearchEntries(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field3", value: "value3" });
results = await FormHistory.search(["timesUsed", "firstUsed", "lastUsed"],
{ fieldname: "field3", value: "value3" });
[timesUsed, firstUsed, lastUsed] = processFirstResult(results);
Assert.equal(11, timesUsed);
Assert.equal(50, firstUsed);
@ -364,7 +323,7 @@ add_task(async function() {
testnum++;
await countDeletedEntries(7);
results = await promiseSearchEntries(["guid"], { fieldname: "field1", value: "value1b" });
results = await FormHistory.search(["guid"], { fieldname: "field1", value: "value1b" });
guid = processFirstResult(results)[3];
await promiseUpdate({ op: "remove", guid});
@ -406,7 +365,7 @@ add_task(async function() {
await promiseUpdate([
{ op: "bump", fieldname: "field5", value: "value5" },
{ op: "bump", fieldname: "field6", value: "value6" }]);
results = await promiseSearchEntries(["fieldname", "timesUsed", "firstUsed", "lastUsed"], { });
results = await FormHistory.search(["fieldname", "timesUsed", "firstUsed", "lastUsed"], { });
Assert.equal(6, results[2].timesUsed);
Assert.equal(13, results[3].timesUsed);