From 068d2fcdd9a964de43d0421ab1cb608a37541f53 Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Fri, 10 May 2024 13:42:58 -0700 Subject: [PATCH 1/2] CI: increase test coverage --- src/types/dats/dat.ts | 11 ---------- test/driveSemaphore.test.ts | 22 ++++++++++++++++++++ test/modules/argumentsParser.test.ts | 18 +++++++++++++++++ test/modules/datMergerSplitter.test.ts | 15 ++++++++++++++ test/modules/directoryCleaner.test.ts | 26 ++++++++++++++++++------ test/modules/romScanner.test.ts | 2 +- test/polyfill/fsPoly.test.ts | 28 ++++++++++++++++++++++++++ test/types/cache.test.ts | 28 ++++++++++++++++++++++++++ test/types/indexedFiles.test.ts | 3 +++ 9 files changed, 135 insertions(+), 18 deletions(-) create mode 100644 test/driveSemaphore.test.ts diff --git a/src/types/dats/dat.ts b/src/types/dats/dat.ts index 6f82deccb..9d1b4aeb7 100644 --- a/src/types/dats/dat.ts +++ b/src/types/dats/dat.ts @@ -107,17 +107,6 @@ export default abstract class DAT { return FsPoly.makeLegal(filename.trim()); } - /** - * Is this a {@link LogiqxDAT} that only contains BIOS files. - */ - isBiosDat(): boolean { - return (this.getGames().length > 0 && this.getGames().every((game) => game.isBios())) - // Redump-style DAT names - || this.getName().match(/(\W|^)BIOS(\W|$)/i) !== null - // libretro-style DAT comments - || (this.getHeader().getComment() ?? '').match(/(\W|^)BIOS(\W|$)/i) !== null; - } - /** * Does a DAT explicitly contain headered ROMs. It is possible for a DAT to be both non-headered * and non-headerless. diff --git a/test/driveSemaphore.test.ts b/test/driveSemaphore.test.ts new file mode 100644 index 000000000..d436d7ed4 --- /dev/null +++ b/test/driveSemaphore.test.ts @@ -0,0 +1,22 @@ +import DriveSemaphore from '../src/driveSemaphore.js'; + +describe('map', () => { + it('should handle thrown errors', async () => { + await expect( + new DriveSemaphore().map( + ['file'], + () => { throw new Error('error'); }, + ), + ).rejects.toThrow('error'); + }); + + it('should handle thrown literals', async () => { + await expect( + new DriveSemaphore().map( + ['file'], + // eslint-disable-next-line @typescript-eslint/no-throw-literal + () => { throw 'message'; }, + ), + ).rejects.toThrow('message'); + }); +}); diff --git a/test/modules/argumentsParser.test.ts b/test/modules/argumentsParser.test.ts index 28a8d478e..80698f3a6 100644 --- a/test/modules/argumentsParser.test.ts +++ b/test/modules/argumentsParser.test.ts @@ -841,6 +841,15 @@ describe('options', () => { expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-language', 'EN,it']).getFilterLanguage()).toEqual(new Set(['EN', 'IT'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-language', 'en,IT,JA']).getFilterLanguage()).toEqual(new Set(['EN', 'IT', 'JA'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-language', 'EN,en']).getFilterLanguage()).toEqual(new Set(['EN'])); + + expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter'])).toThrow(/not enough arguments/i); + expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'XX'])).toThrow(/invalid/i); + expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN,XX'])).toThrow(/invalid/i); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '-L', 'EN']).getFilterLanguage()).toEqual(new Set(['EN'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN']).getFilterLanguage()).toEqual(new Set(['EN'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN,it']).getFilterLanguage()).toEqual(new Set(['EN', 'IT'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'en,IT,JA']).getFilterLanguage()).toEqual(new Set(['EN', 'IT', 'JA'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN,en']).getFilterLanguage()).toEqual(new Set(['EN'])); }); it('should parse "filter-region"', () => { @@ -852,6 +861,15 @@ describe('options', () => { expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-region', 'USA,eur']).getFilterRegion()).toEqual(new Set(['USA', 'EUR'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-region', 'usa,EUR,JPN']).getFilterRegion()).toEqual(new Set(['USA', 'EUR', 'JPN'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-region', 'USA,usa']).getFilterRegion()).toEqual(new Set(['USA'])); + + expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter'])).toThrow(/not enough arguments/i); + expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'XYZ'])).toThrow(/invalid/i); + expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA,XYZ'])).toThrow(/invalid/i); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '-R', 'USA']).getFilterRegion()).toEqual(new Set(['USA'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA']).getFilterRegion()).toEqual(new Set(['USA'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA,eur']).getFilterRegion()).toEqual(new Set(['USA', 'EUR'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'usa,EUR,JPN']).getFilterRegion()).toEqual(new Set(['USA', 'EUR', 'JPN'])); + expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA,usa']).getFilterRegion()).toEqual(new Set(['USA'])); }); it('should parse "no-bios"', () => { diff --git a/test/modules/datMergerSplitter.test.ts b/test/modules/datMergerSplitter.test.ts index 2179d34e4..9e4be81c7 100644 --- a/test/modules/datMergerSplitter.test.ts +++ b/test/modules/datMergerSplitter.test.ts @@ -2292,3 +2292,18 @@ describe('FinalBurn Neo Neo Geo e544671', () => { ]); }); }); + +test.each([ + [MergeMode.NONMERGED], + [MergeMode.SPLIT], + [MergeMode.MERGED], +])('should handle invalid romOf attributes: %s', () => { + // TODO(cemmer) +}); + +test.each([ + [MergeMode.SPLIT], + [MergeMode.MERGED], +])('should handle invalid cloneOf attributes: %s', () => { + // TODO(cemmer) +}); diff --git a/test/modules/directoryCleaner.test.ts b/test/modules/directoryCleaner.test.ts index 0da821cfc..0db790140 100644 --- a/test/modules/directoryCleaner.test.ts +++ b/test/modules/directoryCleaner.test.ts @@ -4,7 +4,7 @@ import Constants from '../../src/constants.js'; import DirectoryCleaner from '../../src/modules/directoryCleaner.js'; import fsPoly from '../../src/polyfill/fsPoly.js'; import File from '../../src/types/files/file.js'; -import Options from '../../src/types/options.js'; +import Options, { OptionsProps } from '../../src/types/options.js'; import ProgressBarFake from '../console/progressBarFake.js'; const ROM_FIXTURES_DIR = path.join('test', 'fixtures', 'roms'); @@ -16,6 +16,7 @@ const ROM_FIXTURES_DIR = path.join('test', 'fixtures', 'roms'); * so it's fine if we implement some workarounds here. */ async function runOutputCleaner( + optionsProps: OptionsProps, cleanExclude: string[], writtenFilePathsToExclude: string[], ): Promise { @@ -31,6 +32,7 @@ async function runOutputCleaner( await new DirectoryCleaner( new Options({ + ...optionsProps, commands: ['move', 'clean'], cleanExclude: cleanExclude.map((filePath) => path.join(tempDir, filePath)), }), @@ -50,7 +52,7 @@ it('should delete nothing if nothing written', async () => { const existingFiles = (await fsPoly.walk(ROM_FIXTURES_DIR)) .map((filePath) => filePath.replace(/^test[\\/]fixtures[\\/]roms[\\/]/, '')) .sort(); - const filesRemaining = await runOutputCleaner([], []); + const filesRemaining = await runOutputCleaner({}, [], []); expect(filesRemaining).toEqual(existingFiles); }); @@ -58,12 +60,12 @@ it('should delete nothing if no excess files', async () => { const existingFiles = (await fsPoly.walk(ROM_FIXTURES_DIR)) .map((filePath) => filePath.replace(/^test[\\/]fixtures[\\/]roms[\\/]/, '')) .sort(); - const filesRemaining = await runOutputCleaner([], existingFiles); + const filesRemaining = await runOutputCleaner({}, [], existingFiles); expect(filesRemaining).toEqual(existingFiles); }); it('should delete some if all unmatched and some excluded', async () => { - const filesRemaining = await runOutputCleaner([ + const filesRemaining = await runOutputCleaner({}, [ path.join('**', 'foobar.*'), ], [ 'non-existent file', @@ -79,7 +81,7 @@ it('should delete some if all unmatched and some excluded', async () => { }); it('should delete some if some matched and nothing excluded', async () => { - const filesRemaining = await runOutputCleaner([], [ + const filesRemaining = await runOutputCleaner({}, [], [ path.join('7z', 'empty.7z'), path.join('raw', 'fizzbuzz.nes'), path.join('zip', 'foobar.zip'), @@ -93,11 +95,23 @@ it('should delete some if some matched and nothing excluded', async () => { }); it('should delete everything if all unmatched and nothing excluded', async () => { - await expect(runOutputCleaner([], [ + await expect(runOutputCleaner({}, [], [ 'non-existent file', ])).resolves.toHaveLength(0); }); +it('should delete nothing if all unmatched but doing a dry run', async () => { + const existingFiles = (await fsPoly.walk(ROM_FIXTURES_DIR)) + .map((filePath) => filePath.replace(/^test[\\/]fixtures[\\/]roms[\\/]/, '')) + .sort(); + const filesRemaining = await runOutputCleaner({ + cleanDryRun: true, + }, [], [ + 'non-existent file', + ]); + expect(filesRemaining).toEqual(existingFiles); +}); + it('should delete hard links', async () => { const tempDir = await fsPoly.mkdtemp(Constants.GLOBAL_TEMP_DIR); try { diff --git a/test/modules/romScanner.test.ts b/test/modules/romScanner.test.ts index c13888ca8..677f6f7c8 100644 --- a/test/modules/romScanner.test.ts +++ b/test/modules/romScanner.test.ts @@ -57,7 +57,7 @@ describe('multiple files', () => { const scannedRealFiles = (await createRomScanner(['test/fixtures/roms']).scan()) .sort((a, b) => a.getFilePath().localeCompare(b.getFilePath())); - // Given some symlinked files + // Given some hard linked files const tempDir = await fsPoly.mkdtemp(Constants.GLOBAL_TEMP_DIR); try { const filesDir = path.join(tempDir, 'files'); diff --git a/test/polyfill/fsPoly.test.ts b/test/polyfill/fsPoly.test.ts index 08afe39d4..5d333544e 100644 --- a/test/polyfill/fsPoly.test.ts +++ b/test/polyfill/fsPoly.test.ts @@ -261,3 +261,31 @@ describe('realpath', () => { await expect(fsPoly.realpath('.')).resolves.toEqual(process.cwd()); }); }); + +describe('touch', () => { + it('should mkdir and touch', async () => { + const tempDir = await fsPoly.mkdtemp(Constants.GLOBAL_TEMP_DIR); + await fsPoly.rm(tempDir, { recursive: true }); + const tempFile = await fsPoly.mktemp(path.join(tempDir, 'temp')); + try { + await fsPoly.touch(tempFile); + await expect(fsPoly.exists(tempFile)).resolves.toEqual(true); + } finally { + await fsPoly.rm(tempDir, { recursive: true, force: true }); + } + }); +}); + +describe('touchSync', () => { + it('should mkdir and touch', async () => { + const tempDir = await fsPoly.mkdtemp(Constants.GLOBAL_TEMP_DIR); + await fsPoly.rm(tempDir, { recursive: true }); + const tempFile = await fsPoly.mktemp(path.join(tempDir, 'temp')); + try { + fsPoly.touchSync(tempFile); + await expect(fsPoly.exists(tempFile)).resolves.toEqual(true); + } finally { + await fsPoly.rm(tempDir, { recursive: true, force: true }); + } + }); +}); diff --git a/test/types/cache.test.ts b/test/types/cache.test.ts index bebb4ab6b..377d3bd68 100644 --- a/test/types/cache.test.ts +++ b/test/types/cache.test.ts @@ -165,6 +165,34 @@ describe('set', () => { }); }); +describe('delete', () => { + it('should delete a single key', async () => { + const cache = new Cache(); + + await cache.set('key', 'value'); + await expect(cache.has('key')).resolves.toEqual(true); + + await cache.delete('key'); + await expect(cache.has('key')).resolves.toEqual(false); + }); + + it('should delete regex-matched keys', async () => { + const cache = new Cache(); + + await cache.set('key1', 'value'); + await expect(cache.has('key1')).resolves.toEqual(true); + await cache.set('key2', 'value'); + await expect(cache.has('key2')).resolves.toEqual(true); + await cache.set('key3', 'value'); + await expect(cache.has('key3')).resolves.toEqual(true); + + await cache.delete(/key[12]/); + await expect(cache.has('key1')).resolves.toEqual(false); + await expect(cache.has('key2')).resolves.toEqual(false); + await expect(cache.has('key3')).resolves.toEqual(true); + }); +}); + describe('load', () => { it('should not throw on nonexistent file', async () => { const tempFile = await FsPoly.mktemp(path.join(Constants.GLOBAL_TEMP_DIR, 'cache')); diff --git a/test/types/indexedFiles.test.ts b/test/types/indexedFiles.test.ts index dd7362040..3b1028d16 100644 --- a/test/types/indexedFiles.test.ts +++ b/test/types/indexedFiles.test.ts @@ -1,5 +1,6 @@ import ROM from '../../src/types/dats/rom.js'; import File from '../../src/types/files/file.js'; +import ROMHeader from '../../src/types/files/romHeader.js'; import IndexedFiles from '../../src/types/indexedFiles.js'; describe('findFiles', () => { @@ -14,6 +15,7 @@ describe('findFiles', () => { size: 2, crc32: '22222222', md5: '22222222222222222222222222222222', + fileHeader: ROMHeader.headerFromFilename('two.lnx'), }), File.fileOf({ filePath: 'three', @@ -36,6 +38,7 @@ describe('findFiles', () => { size: 6, sha1: '6666666666666666666666666666666666666666', sha256: '6666666666666666666666666666666666666666666666666666666666666666', + fileHeader: ROMHeader.headerFromFilename('six.nes'), }), File.fileOf({ filePath: 'seven', From 449e3969f720b3aa454d4eeecc68f2be358a80fe Mon Sep 17 00:00:00 2001 From: Christian Emmer <10749361+emmercm@users.noreply.github.com> Date: Fri, 10 May 2024 13:48:09 -0700 Subject: [PATCH 2/2] Fix test --- test/modules/argumentsParser.test.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/modules/argumentsParser.test.ts b/test/modules/argumentsParser.test.ts index 80698f3a6..8e7d6d084 100644 --- a/test/modules/argumentsParser.test.ts +++ b/test/modules/argumentsParser.test.ts @@ -843,8 +843,6 @@ describe('options', () => { expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-language', 'EN,en']).getFilterLanguage()).toEqual(new Set(['EN'])); expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter'])).toThrow(/not enough arguments/i); - expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'XX'])).toThrow(/invalid/i); - expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN,XX'])).toThrow(/invalid/i); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '-L', 'EN']).getFilterLanguage()).toEqual(new Set(['EN'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN']).getFilterLanguage()).toEqual(new Set(['EN'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--language-filter', 'EN,it']).getFilterLanguage()).toEqual(new Set(['EN', 'IT'])); @@ -863,8 +861,6 @@ describe('options', () => { expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--filter-region', 'USA,usa']).getFilterRegion()).toEqual(new Set(['USA'])); expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter'])).toThrow(/not enough arguments/i); - expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'XYZ'])).toThrow(/invalid/i); - expect(() => argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA,XYZ'])).toThrow(/invalid/i); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '-R', 'USA']).getFilterRegion()).toEqual(new Set(['USA'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA']).getFilterRegion()).toEqual(new Set(['USA'])); expect(argumentsParser.parse([...dummyCommandAndRequiredArgs, '--region-filter', 'USA,eur']).getFilterRegion()).toEqual(new Set(['USA', 'EUR']));