Skip to content

Commit

Permalink
CI: increase test coverage (#1131)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmercm authored May 10, 2024
1 parent 2418458 commit 7adcf96
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 49 deletions.
5 changes: 3 additions & 2 deletions src/igir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export default class Igir {
? this.options.getCachePath() ?? Constants.GLOBAL_CACHE_FILE
: undefined;
if (cachePath !== undefined) {
this.logger.trace(`setting the file cache path to '${cachePath}'`);
FileCache.loadFile(cachePath);
this.logger.trace(`loading the file cache at '${cachePath}'`);
await FileCache.loadFile(cachePath);
} else {
this.logger.trace('not using a file for the file cache');
}
Expand Down Expand Up @@ -322,6 +322,7 @@ export default class Igir {

const preferredCandidates = await new CandidatePreferer(this.options, progressBar)
.prefer(dat, patchedCandidates);
// TODO(cemmer): calculate raw checksums for archives after applying 1G1R rules

const postProcessedCandidates = await new CandidatePostProcessor(this.options, progressBar)
.process(dat, preferredCandidates);
Expand Down
85 changes: 47 additions & 38 deletions src/types/files/fileCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,52 @@ enum ValueType {
export default class FileCache {
private static readonly VERSION = 3;

private static cache: Cache<CacheValue> | Promise<Cache<CacheValue>> = new Cache<CacheValue>();
private static cache: Cache<CacheValue> = new Cache<CacheValue>();

private static enabled = true;

public static disable(): void {
this.enabled = false;
}

public static loadFile(filePath: string): void {
this.cache = new Cache<CacheValue>({
public static async loadFile(filePath: string): Promise<void> {
this.cache = await new Cache<CacheValue>({
filePath,
fileFlushMillis: 30_000,
})
.load()
// Cleanup the loaded cache file
.then(async (cache) => {
// Delete keys from old cache versions
await Promise.all([...Array.from({ length: FileCache.VERSION }).keys()].slice(1)
.map(async (prevVersion) => {
const keyRegex = new RegExp(`^V${prevVersion}\\|`);
return cache.delete(keyRegex);
}));
return cache;
})
.then(async (cache) => {
// Delete keys for deleted files
const disks = FsPoly.disksSync();
const semaphore = new Semaphore(Constants.MAX_FS_THREADS);
await Promise.all([...cache.keys()].map(async (cacheKey) => {
const cacheKeyFilePath = cacheKey.split('|')[1];
if (!disks.some((disk) => cacheKeyFilePath.startsWith(disk))) {
// Don't delete the key if it's for a disk that isn't mounted right now
return;
}
await semaphore.runExclusive(async () => {
if (!await FsPoly.exists(cacheKeyFilePath)) {
// If the file no longer exists then delete its key from the cache
await cache.delete(cacheKeyFilePath);
}
});
}));
return cache;
}).load();

// Cleanup the loaded cache file
// Delete keys from old cache versions
await Promise.all([...Array.from({ length: FileCache.VERSION }).keys()].slice(1)
.map(async (prevVersion) => {
const keyRegex = new RegExp(`^V${prevVersion}\\|`);
return this.cache.delete(keyRegex);
}));

// Delete keys for deleted files
const disks = FsPoly.disksSync();
const semaphore = new Semaphore(Constants.MAX_FS_THREADS);
await Promise.all([...this.cache.keys()].map(async (cacheKey) => {
const cacheKeyFilePath = cacheKey.split('|')[1];
if (!disks.some((disk) => cacheKeyFilePath.startsWith(disk))) {
// Don't delete the key if it's for a disk that isn't mounted right now
return;
}
await semaphore.runExclusive(async () => {
if (!await FsPoly.exists(cacheKeyFilePath)) {
// If the file no longer exists, then delete its key from the cache
await this.cache.delete(cacheKeyFilePath);
}
});
}));
}

public static async save(): Promise<void> {
if (!this.enabled) {
return;
}

await this.cache.save();
}

static async getOrComputeFile(
Expand All @@ -82,7 +85,7 @@ export default class FileCache {
// NOTE(cemmer): we're using the cache as a mutex here, so even if this function is called
// multiple times concurrently, entries will only be fetched once.
let computedFile: File | undefined;
const cachedValue = await (await this.cache).getOrCompute(
const cachedValue = await this.cache.getOrCompute(
cacheKey,
async () => {
computedFile = await File.fileOf({ filePath }, checksumBitmask);
Expand Down Expand Up @@ -117,7 +120,14 @@ export default class FileCache {

// We didn't compute the file (cache hit), deserialize the properties into a full object
const cachedFile = cachedValue.value as FileProps;
return File.fileOfObject(filePath, cachedFile);
return File.fileOfObject(filePath, {
...cachedFile,
// Only return the checksums requested
crc32: checksumBitmask & ChecksumBitmask.CRC32 ? cachedFile.crc32 : undefined,
md5: checksumBitmask & ChecksumBitmask.MD5 ? cachedFile.md5 : undefined,
sha1: checksumBitmask & ChecksumBitmask.SHA1 ? cachedFile.sha1 : undefined,
sha256: checksumBitmask & ChecksumBitmask.SHA256 ? cachedFile.sha256 : undefined,
});
}

static async getOrComputeEntries<T extends Archive>(
Expand All @@ -135,7 +145,7 @@ export default class FileCache {
// NOTE(cemmer): we're using the cache as a mutex here, so even if this function is called
// multiple times concurrently, entries will only be fetched once.
let computedEntries: ArchiveEntry<T>[] | undefined;
const cachedValue = await (await this.cache).getOrCompute(
const cachedValue = await this.cache.getOrCompute(
cacheKey,
async () => {
computedEntries = await archive.getArchiveEntries(checksumBitmask) as ArchiveEntry<T>[];
Expand Down Expand Up @@ -171,9 +181,8 @@ export default class FileCache {
// We didn't compute the archive entries (cache hit), deserialize the properties into
// full objects
const cachedEntries = cachedValue.value as ArchiveEntryProps<T>[];
return Promise.all(cachedEntries.map(async (props) => ArchiveEntry.entryOf({
return Promise.all(cachedEntries.map(async (props) => ArchiveEntry.entryOfObject(archive, {
...props,
archive,
// Only return the checksums requested
crc32: checksumBitmask & ChecksumBitmask.CRC32 ? props.crc32 : undefined,
md5: checksumBitmask & ChecksumBitmask.MD5 ? props.md5 : undefined,
Expand Down
18 changes: 9 additions & 9 deletions test/fixtures/dats/patchable.dat
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,60 @@
<game name="92C85C9">
<description>92C85C9</description>
<rom name="92C85C9.rom" size="65536" crc="06692159" md5="8e0caf88ba437cb7109c5642e06cfdb2" sha1="8335c4505380035c2fa94e2a1b9e69517e746905" status="verified"/>
<!-- After: size="65536" crc="b13eb478" -->
<!-- After: name="04C896D-GBA.rom" size="65536" crc="b13eb478" -->
</game>
<!-- APS (N64 SIMPLE) -->
<game name="3708F2C">
<description>3708F2C</description>
<rom name="3708F2C.rom" size="1025" crc="20891c9f" md5="b191e2d913681475ef6a579d4b40bb1f" sha1="181e0713260060fa7d6c64292902023ca7c88a06" status="verified"/>
<!-- After: size="1025" crc="caaaf550" -->
<!-- After: name="DFF7872-N64-SIMPLE.rom" size="1025" crc="caaaf550" -->
</game>

<!-- IPS -->
<game name="Before">
<description>Before</description>
<rom name="Before.rom" size="7" crc="0361b321" md5="ab8d71b3fdce92efd8bdf29cffd36116" sha1="7473def7220a59a6b4146e629064e1456542242d" status="verified"/>
<!-- After: size="7" crc="4c8e44d4" -->
<!-- After: name="After.rom" size="7" crc="4c8e44d4" -->
</game>
<!-- IPS32 -->
<game name="65D1206">
<description>65D1206</description>
<rom name="65D1206.rom" size="1025" crc="20323455" md5="5ab0c664d1ed83757fbe49e99c509cb0" sha1="31306e0f014d3ac2c24d2abede5f9a67c097c5e7" status="verified"/>
<!-- After: size=1025" crc="8bb5cc63" -->
<!-- After: name="9E66269.rom" size=1025" crc="8bb5cc63" -->
</game>

<!-- BPS -->
<game name="Best">
<description>Best</description>
<rom name="Best.rom" size="25" crc="1e3d78cf" md5="5212c8404bf1de024f9fb62289b68474" sha1="727d8db3dbfb5960057cb62e8b47d0e2842a4d55" status="verified"/>
<!-- After: size="26" crc="6ff9ef96" -->
<!-- After: name="Worst.rom" size="26" crc="6ff9ef96" -->
</game>

<!-- NINJA2 -->
<game name="612644F">
<description>612644F</description>
<rom name="612644F.rom" size="1025" crc="f7591b29" md5="92c62becd006bac8175304bb80dfb3aa" sha1="2bcd0216e1de4ee670ee4945d600beaf6acf8feb" status="verified"/>
<!-- After: size="1025" crc="922f5181" -->
<!-- After: name="9A71FA5.rom" size="1025" crc="922f5181" -->
</game>

<!-- PPF -->
<game name="C01173E">
<description>C01173E</description>
<rom name="C01173E.rom" size="1025" crc="dfaebe28" md5="c9e35aafaec606fcb16763200ce35b0f" sha1="2f185826482db2878663c0cd393d62d0c10814ae" status="verified"/>
<!-- After: size="1025" crc="95284ab4" -->
<!-- After: name="949F2B7.rom" size="1025" crc="95284ab4" -->
</game>

<!-- UPS -->
<game name="KDULVQN">
<description>KDULVQN</description>
<rom name="KDULVQN.rom" size="1025" crc="b1c303e4" md5="36109a5d2f9041b6ec00d7aea63379f3" sha1="6bc1e23ffaf5fafaf03fb10732b6be3b567b1417" status="verified"/>
<!-- After: size="1025" crc="e02c6dbb" -->
<!-- After: name="DDSK3AN.rom" size="1025" crc="e02c6dbb" -->
</game>

<!-- vcdiff -->
<game name="0F09A40">
<description>0F09A40</description>
<rom name="0F09A40.rom" size="1025" crc="2f943e86" md5="02784c56583f4d82d11aec4c57f2859c" sha1="7b5b3998062b8338324b5f69ffcdcc22087ccbd5" status="verified"/>
<!-- After: size="1025" crc="1fb4f81f" -->
<!-- After: name="4FE952A.rom" size="1025" crc="1fb4f81f" -->
</game>
</datafile>
21 changes: 21 additions & 0 deletions test/types/files/fileCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import path from 'node:path';

import Constants from '../../../src/constants.js';
import FsPoly from '../../../src/polyfill/fsPoly.js';
import Zip from '../../../src/types/files/archives/zip.js';
import FileCache from '../../../src/types/files/fileCache.js';
import { ChecksumBitmask } from '../../../src/types/files/fileChecksums.js';

describe('loadFile', () => {
it('should load after saving', async () => {
const tempCache = await FsPoly.mktemp(path.join(Constants.GLOBAL_TEMP_DIR, 'cache'));
await FileCache.loadFile(tempCache);

// Compute some values
await FileCache.getOrComputeFile(path.join('test', 'fixtures', 'roms', 'raw', 'fizzbuzz.nes'), ChecksumBitmask.CRC32);
await FileCache.getOrComputeEntries(new Zip(path.join('test', 'fixtures', 'roms', 'zip', 'foobar.zip')), ChecksumBitmask.CRC32);

await FileCache.save();
await FileCache.loadFile(tempCache);
});
});

0 comments on commit 7adcf96

Please sign in to comment.