Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
- Manifest: netwerk/test/unit/xpcshell.toml
"use strict";
// An entry file must only be rewritten when something about the entry actually
// changed. Two ways it used to be rewritten for nothing:
//
// 1. CacheFileMetadata::ParseMetadata() marked the freshly parsed metadata
// dirty unconditionally, so the first read-only hit on an entry that had to
// be reloaded from disk (after a restart, or after the entry was purged
// from the memory pool) rewrote the whole metadata block.
//
// 2. CacheFileMetadata::SetElement() dirtied the metadata even when the
// element already held that exact value. nsHttpChannel does this on every
// HTTPS cache hit: it reads security-info out of the entry and stores it
// straight back in CloseCacheEntry().
//
// Both are pure IO, and on Windows every rewrite makes on-access anti-malware
const META = "meta";
const META_CHANGED = "meta-changed";
const DATA = "0123456789";
// Opens the entry, optionally runs |fn| on it, and deliberately does not let
// the entry escape: a lingering JS reference would keep the CacheEntry alive
// and stop settle() below from purging (and thus flushing) it.
function openEntry(behavior, meta, fn) {
return new Promise(resolve => {
asyncOpenCacheEntry(
URL,
"disk",
Ci.nsICacheStorage.OPEN_NORMALLY,
null,
new OpenCallback(behavior, meta, DATA, entry => {
if (fn) {
fn(entry);
}
resolve();
})
);
});
}
// Purging the entry from the memory pool destroys its CacheFile, which writes
// the metadata back out if it is dirty. That is the barrier this test uses:
// after settle() the entry file on disk reflects every pending change.
async function settle() {
Cu.forceGC();
Cu.forceCC();
Cu.forceGC();
await new Promise(resolve => {
Services.cache2.QueryInterface(Ci.nsICacheTesting).flush({
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
observe() {
resolve();
},
});
});
}
async function readSingleEntryFile() {
let dir = getDiskCacheDirectory();
dir.append("entries");
Assert.ok(dir.exists(), "entries directory exists");
let files = [];
let e = dir.directoryEntries;
while (e.hasMoreElements()) {
files.push(e.nextFile);
}
Assert.equal(files.length, 1, "exactly one entry file on disk");
return IOUtils.read(files[0].path);
}
function bytesEqual(a, b) {
if (a.length != b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] != b[i]) {
return false;
}
}
return true;
}
add_task(async function test_entry_file_only_rewritten_on_real_change() {
do_get_profile();
// Persist the index eagerly so a reloaded entry can be seeded from it.
Services.prefs.setIntPref(
"browser.cache.disk.index.min_unwritten_changes",
1
);
Services.prefs.setIntPref("browser.cache.disk.index.min_dump_interval_ms", 0);
// Create the entry and get it fully written out.
await openEntry(NEW | WAITFORWRITE, META);
await new Promise(wait_for_cache_index);
await settle();
let baseline = await readSingleEntryFile();
// Simulate a restart: the in-memory entry table is dropped, so the next open
// has to read and parse the metadata back from the entry file.
let testing = Services.cache2.QueryInterface(Ci.nsICacheTesting);
testing.shutdownCacheForTesting();
testing.startupCacheForTesting();
await new Promise(wait_for_cache_index);
// A plain read-only hit on the reloaded entry.
await openEntry(NORMAL, META);
await settle();
Assert.ok(
bytesEqual(baseline, await readSingleEntryFile()),
"entry file unchanged after a read-only hit on a reloaded entry"
);
// Storing a metadata element with the value it already holds changes nothing.
await openEntry(NORMAL, META, entry => {
entry.setMetaDataElement("meto", META);
});
await settle();
Assert.ok(
bytesEqual(baseline, await readSingleEntryFile()),
"entry file unchanged after re-storing an identical metadata element"
);
// Control: a metadata element that really changes is still written out, so
// the assertions above are not passing just because nothing is ever written.
await openEntry(NORMAL, META, entry => {
entry.setMetaDataElement("meto", META_CHANGED);
});
await settle();
Assert.ok(
!bytesEqual(baseline, await readSingleEntryFile()),
"entry file rewritten after a real metadata change"
);
// ...and the new value is what a later open sees.
await openEntry(NORMAL, META_CHANGED);
});