Skip to content

Commit

Permalink
Add command line options to regression.js.
Browse files Browse the repository at this point in the history
  • Loading branch information
johnkenny54 committed Sep 18, 2024
1 parent d5166e6 commit 34ca507
Showing 1 changed file with 96 additions and 148 deletions.
244 changes: 96 additions & 148 deletions test/regression.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import fs from 'node:fs/promises';
import { cwd } from 'node:process';
import { fileURLToPath } from 'node:url';
import http from 'http';
import os from 'os';
import path from 'path';
import { program } from 'commander';
import colors from 'picocolors';
import pixelmatch from 'pixelmatch';
import { chromium } from 'playwright';
import { PNG } from 'pngjs';
import { fileURLToPath } from 'url';
import { optimize } from '../lib/svgo.js';
import { toFixed } from '../lib/svgo/tools.js';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

Expand All @@ -21,121 +24,53 @@ const screenshotOptions = {
animations: 'disabled',
};

/** @type {Object<string,{expectedMismatches:number,comment?:string}>}*/
const mismatchExceptions = {
'svgs/oxygen-icons-5.113.0/scalable/actions/document-close.svg': {
expectedMismatches: 2,
comment: 'No mismatches when width/height removed.',
},
'svgs/oxygen-icons-5.113.0/scalable/actions/small/16x16/application-exit.svg':
{ expectedMismatches: 1 },
'svgs/oxygen-icons-5.113.0/scalable/actions/small/16x16/view-media-playlist.svg':
{ expectedMismatches: 1 },
'svgs/oxygen-icons-5.113.0/scalable/actions/small/22x22/view-time-schedule-calculus.svg':
{ expectedMismatches: 2 },
'svgs/oxygen-icons-5.113.0/scalable/actions/small/48x48/preflight-verifier.svg':
{
expectedMismatches: 24,
comment:
'No visual difference; errors go away when width/height are removed.',
},
'svgs/oxygen-icons-5.113.0/scalable/categories/system-help.svg': {
expectedMismatches: 9,
comment: 'no visual difference with or without width/height',
},
'svgs/oxygen-icons-5.113.0/scalable/categories/small/22x22/system-help.svg': {
expectedMismatches: 2,
comment:
'No visual difference with or without width/height. Without width/height, 33 pixel mismatches but still can not see the difference.',
},
'svgs/oxygen-icons-5.113.0/scalable/devices/hidef/media-flash-smart-media.svg':
{ expectedMismatches: 1, comment: 'works if width/height are removed' },
'svgs/oxygen-icons-5.113.0/scalable/apps/small/16x16/preferences-desktop-user.svg':
{ expectedMismatches: 9 },
'svgs/oxygen-icons-5.113.0/scalable/apps/small/32x32/preferences-desktop-user.svg':
{ expectedMismatches: 1 },
'svgs/oxygen-icons-5.113.0/scalable/apps/preferences-desktop-user.svg': {
expectedMismatches: 1,
},
'svgs/W3C_SVG_11_TestSuite/svg/filters-composite-02-b.svg': {
expectedMismatches: 4,
comment: 'No visual differences.',
},
'svgs/W3C_SVG_11_TestSuite/svg/filters-gauss-01-b.svg': {
expectedMismatches: 4,
comment: 'No visual differences.',
},
};

/**
* @param {string[]} list
* @returns {Promise<boolean>}
* @param {import('commander').OptionValues} options
*/
const runTests = async (list) => {
async function performTests(options) {
let mismatched = 0;
let passed = 0;
console.info('Start browser…');
const notOptimized = new Set();
let totalInputSize = 0;
let totalCompression = 0;

/** @type {import('../lib/svgo.js').Config} */
const config = {};
config.preset = options.preset;
config.enable = options.enable;

/**
* @param {import('playwright').Page} page
* @param {string} name
* @param {string[]} list
* @returns {Promise<boolean>}
*/
const processFile = async (page, name) => {
await page.goto(`http://localhost:5000/original/${name}`);
const originalBuffer = await page.screenshot(screenshotOptions);
await page.goto(`http://localhost:5000/optimized/${name}`);
const optimizedBufferPromise = page.screenshot(screenshotOptions);
async function runTests(list) {
console.info('Start browser…');
/**
* @param {import('playwright').Page} page
* @param {string} name
*/
const processFile = async (page, name) => {
await page.goto(`http://localhost:5000/original/${name}`);
const originalBuffer = await page.screenshot(screenshotOptions);
await page.goto(`http://localhost:5000/optimized/${name}`);
const optimizedBufferPromise = page.screenshot(screenshotOptions);

const writeDiffs = process.env.NO_DIFF == null;
const diff = writeDiffs && new PNG({ width, height });
const originalPng = PNG.sync.read(originalBuffer);
const optimizedPng = PNG.sync.read(await optimizedBufferPromise);
const mismatchCount = pixelmatch(
originalPng.data,
optimizedPng.data,
diff ? diff.data : null,
width,
height,
);
const exception = mismatchExceptions[name];
if (exception) {
if (mismatchCount === 0) {
passed++;
console.warn(
colors.yellow(`${name} matches but is listed as exception`),
);
} else {
if (exception.expectedMismatches === mismatchCount) {
if (exception.comment) {
console.info(
colors.green(
`${name} is in ignore list and has ${mismatchCount} pixel mismatches`,
),
);
} else {
console.info(
colors.cyan(
`${name} is in ignore list and has ${mismatchCount} pixel mismatches and no comment`,
),
);
}
} else {
mismatched++;
console.error(
colors.red(
`${name} is in ignore list with ${exception.expectedMismatches} expected pixel mismatches but got ${mismatchCount}`,
),
);
}
const writeDiffs = process.env.NO_DIFF == null;
const diff = writeDiffs && new PNG({ width, height });
const originalPng = PNG.sync.read(originalBuffer);
const optimizedPng = PNG.sync.read(await optimizedBufferPromise);
const mismatchCount = pixelmatch(
originalPng.data,
optimizedPng.data,
diff ? diff.data : null,
width,
height,
);
if (notOptimized.has(name)) {
return;
}
} else if (mismatchCount <= 0) {
passed++;
} else {
if (mismatchCount === 1) {
console.info(
colors.cyan(
`${name} is not in ignore list but has only 1 pixel mismatch`,
),
);
if (mismatchCount <= 0) {
passed++;
} else {
mismatched++;
console.error(
Expand All @@ -151,35 +86,38 @@ const runTests = async (list) => {
await fs.writeFile(file, PNG.sync.write(diff));
}
}
}
};
const worker = async () => {
let item;
const page = await context.newPage();
while ((item = list.pop())) {
await processFile(page, item);
}
await page.close();
};
};

const browser = await chromium.launch();
const context = await browser.newContext({
javaScriptEnabled: false,
viewport: { width, height },
});
await Promise.all(
Array.from(new Array(os.cpus().length * 2), () => worker()),
);
await browser.close();
console.info(`Mismatched: ${mismatched}`);
console.info(`Passed: ${passed}`);
return mismatched === 0;
};
const worker = async () => {
let item;
const page = await context.newPage();
while ((item = list.pop())) {
await processFile(page, item);
}
await page.close();
};

const browser = await chromium.launch();
const context = await browser.newContext({
javaScriptEnabled: false,
viewport: { width, height },
});
await Promise.all(
Array.from(new Array(os.cpus().length * 2), () => worker()),
);
await browser.close();
console.info(`Mismatched: ${mismatched}`);
console.info(`Not Optimized: ${notOptimized.size}`);
console.info(`Passed: ${passed}`);
console.info(
`Total Compression: ${totalCompression} bytes (${toFixed((totalCompression / totalInputSize) * 100, 2)}%)`,
);
return mismatched === 0;
}

(async () => {
try {
const start = process.hrtime.bigint();
const fixturesDir = path.join(__dirname, 'regression-fixtures');
const fixturesDir = path.join(cwd(), options.inputdir);
const filesPromise = fs.readdir(fixturesDir, { recursive: true });
const server = http.createServer(async (req, res) => {
if (req.url === undefined) {
Expand All @@ -201,21 +139,12 @@ const runTests = async (list) => {
return;
}
if (req.url.startsWith('/optimized/')) {
/** @type {import('../lib/svgo.js').Config} */
const config = {};
config.plugins = [
{
name: 'preset-default',
params: {
overrides: {
// convertShapeToPath causes a number of false positives
convertShapeToPath: false,
},
},
},
];

const optimized = optimize(file, config);
if (optimized.error) {
notOptimized.add(name.substring(1));
}
totalInputSize += file.length;
totalCompression += file.length - optimized.data.length;
res.setHeader('Content-Type', 'image/svg+xml');
res.end(optimized.data);
return;
Expand All @@ -241,4 +170,23 @@ const runTests = async (list) => {
console.error(error);
process.exit(1);
}
})();
}

program
.option(
'--preset <default | none>',
'Specify which set of predefined plugins to use',
'default',
)
.option(
'--enable <plugin...>',
'Specify one or more builtin plugins to run in addition to the presets',
)
.option(
'--inputdir <dir>',
'Location of input files',
'./test/regression-fixtures',
)
.action(performTests);

program.parseAsync();

0 comments on commit 34ca507

Please sign in to comment.