Skip to content

Commit d89d36e

Browse files
authored
Split regression extracter and runner (#1451)
A lot of new sources of regression tests may come and it's important to make debug simpler. Now regression-extract.js downloads and write svg files into test/regression-fixtures. regression.js run each svg in this folder. Mismatched svg diff is written into test/regression-diffs.
1 parent eb934b4 commit d89d36e

File tree

5 files changed

+133
-115
lines changed

5 files changed

+133
-115
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
node_modules
22
dist
3-
diffs
3+
test/regression-fixtures
4+
test/regression-diffs
45
coverage
56
bin/svgo-profiling
67
.DS_Store

package-lock.json

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"fix": "eslint --ignore-path .gitignore --fix . && prettier --write \"**/*.js\" --ignore-path .gitignore",
5555
"typecheck": "tsc",
5656
"test-browser": "rollup -c && node ./test/browser.js",
57-
"test-regression": "NO_DIFF=1 node ./test/regression.js",
57+
"test-regression": "node ./test/regression-extract.js && NO_DIFF=1 node ./test/regression.js",
5858
"prepublishOnly": "rm -rf dist && rollup -c"
5959
},
6060
"prettier": {
@@ -109,7 +109,6 @@
109109
"chai": "^4.3.4",
110110
"del": "^6.0.0",
111111
"eslint": "^7.22.0",
112-
"get-stream": "^6.0.0",
113112
"mocha": "^8.3.2",
114113
"mock-stdin": "^1.0.0",
115114
"node-fetch": "^2.6.1",

test/regression-extract.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const path = require('path');
5+
const util = require('util');
6+
const zlib = require('zlib');
7+
const stream = require('stream');
8+
const fetch = require('node-fetch');
9+
const tarStream = require('tar-stream');
10+
11+
const pipeline = util.promisify(stream.pipeline);
12+
13+
const extractTarGz = async (url, baseDir, include) => {
14+
const extract = tarStream.extract();
15+
extract.on('entry', async (header, stream, next) => {
16+
try {
17+
if (include == null || include.test(header.name)) {
18+
if (header.name.endsWith('.svg')) {
19+
const file = path.join(baseDir, header.name);
20+
await fs.promises.mkdir(path.dirname(file), { recursive: true });
21+
await pipeline(stream, fs.createWriteStream(file));
22+
}
23+
if (header.name.endsWith('.svgz')) {
24+
// .svgz -> .svg
25+
const file = path.join(baseDir, header.name.slice(0, -1));
26+
await fs.promises.mkdir(path.dirname(file), { recursive: true });
27+
await pipeline(
28+
stream,
29+
zlib.createGunzip(),
30+
fs.createWriteStream(file)
31+
);
32+
}
33+
}
34+
} catch (error) {
35+
console.error(error);
36+
process.exit(1);
37+
}
38+
stream.resume();
39+
next();
40+
});
41+
const response = await fetch(url);
42+
await pipeline(response.body, extract);
43+
};
44+
45+
(async () => {
46+
try {
47+
console.info('Download W3C SVG 1.1 Test Suite and extract svg files');
48+
await extractTarGz(
49+
'https://www.w3.org/Graphics/SVG/Test/20110816/archives/W3C_SVG_11_TestSuite.tar.gz',
50+
path.join(__dirname, 'regression-fixtures', 'w3c-svg-11-test-suite'),
51+
/^svg\//
52+
);
53+
} catch (error) {
54+
console.error(error);
55+
process.exit(1);
56+
}
57+
})();

test/regression.js

Lines changed: 73 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -2,92 +2,12 @@
22

33
const fs = require('fs');
44
const path = require('path');
5-
const util = require('util');
6-
const zlib = require('zlib');
75
const http = require('http');
8-
const stream = require('stream');
9-
const fetch = require('node-fetch');
10-
const tarStream = require('tar-stream');
11-
const getStream = require('get-stream');
126
const { chromium } = require('playwright');
137
const { PNG } = require('pngjs');
148
const pixelmatch = require('pixelmatch');
159
const { optimize } = require('../lib/svgo.js');
1610

17-
const pipeline = util.promisify(stream.pipeline);
18-
19-
const readSvgFiles = async () => {
20-
const cachedArchiveFile = path.join(
21-
process.cwd(),
22-
'node_modules/.cache/W3C_SVG_11_TestSuite.tar.gz'
23-
);
24-
const svgFiles = new Map();
25-
let fileStream;
26-
try {
27-
await fs.promises.access(cachedArchiveFile);
28-
fileStream = fs.createReadStream(cachedArchiveFile);
29-
} catch {
30-
const response = await fetch(
31-
'https://www.w3.org/Graphics/SVG/Test/20110816/archives/W3C_SVG_11_TestSuite.tar.gz'
32-
);
33-
fileStream = response.body;
34-
fileStream.pipe(fs.createWriteStream(cachedArchiveFile));
35-
}
36-
const extract = tarStream.extract();
37-
extract.on('entry', async (header, stream, next) => {
38-
try {
39-
if (header.name.startsWith('svg/')) {
40-
if (header.name.endsWith('.svg')) {
41-
// strip folder and extension
42-
const name = header.name.slice('svg/'.length, -'.svg'.length);
43-
const string = await getStream(stream);
44-
svgFiles.set(name, string);
45-
}
46-
if (header.name.endsWith('.svgz')) {
47-
// strip folder and extension
48-
const name = header.name.slice('svg/'.length, -'.svgz'.length);
49-
const string = await getStream(stream.pipe(zlib.createGunzip()));
50-
svgFiles.set(name, string);
51-
}
52-
}
53-
} catch (error) {
54-
console.error(error);
55-
process.exit(1);
56-
}
57-
stream.resume();
58-
next();
59-
});
60-
await pipeline(fileStream, extract);
61-
return svgFiles;
62-
};
63-
64-
const optimizeSvgFiles = (svgFiles) => {
65-
const optimizedFiles = new Map();
66-
let failed = 0;
67-
for (const [name, string] of svgFiles) {
68-
try {
69-
const result = optimize(string, { path: name, floatPrecision: 4 });
70-
if (result.error) {
71-
console.error(result.error);
72-
console.error(`File: ${name}`);
73-
failed += 1;
74-
continue;
75-
} else {
76-
optimizedFiles.set(name, result.data);
77-
}
78-
} catch (error) {
79-
console.error(error);
80-
console.error(`File: ${name}`);
81-
failed += 1;
82-
continue;
83-
}
84-
}
85-
if (failed !== 0) {
86-
throw Error(`Failed to optimize ${failed} cases`);
87-
}
88-
return optimizedFiles;
89-
};
90-
9111
const chunkInto = (array, chunksCount) => {
9212
// take upper bound to include tail
9313
const chunkSize = Math.ceil(array.length / chunksCount);
@@ -99,37 +19,39 @@ const chunkInto = (array, chunksCount) => {
9919
return result;
10020
};
10121

102-
const runTests = async ({ svgFiles }) => {
22+
const runTests = async ({ list }) => {
10323
let skipped = 0;
10424
let mismatched = 0;
10525
let passed = 0;
10626
console.info('Start browser...');
10727
const processFile = async (page, name) => {
10828
if (
10929
// hard to detect the end of animation
110-
name.startsWith('animate-') ||
30+
name.startsWith('w3c-svg-11-test-suite/svg/animate-') ||
11131
// breaks because of optimisation despite of script
112-
name === 'interact-pointer-04-f' ||
32+
name === 'w3c-svg-11-test-suite/svg/interact-pointer-04-f.svg' ||
11333
// messed gradients
114-
name === 'pservers-grad-18-b' ||
34+
name === 'w3c-svg-11-test-suite/svg/pservers-grad-18-b.svg' ||
11535
// animated filter
116-
name === 'filters-light-04-f' ||
36+
name === 'w3c-svg-11-test-suite/svg/filters-light-04-f.svg' ||
37+
// animated filter
38+
name === 'w3c-svg-11-test-suite/svg/filters-composite-05-f.svg' ||
11739
// removing wrapping <g> breaks :first-child pseudo-class
118-
name === 'styling-pres-04-f' ||
40+
name === 'w3c-svg-11-test-suite/svg/styling-pres-04-f.svg' ||
11941
// messed case insensitivity while inlining styles
120-
name === 'styling-css-10-f' ||
42+
name === 'w3c-svg-11-test-suite/svg/styling-css-10-f.svg' ||
12143
// rect is converted to path which matches wrong styles
122-
name === 'styling-css-08-f' ||
44+
name === 'w3c-svg-11-test-suite/svg/styling-css-08-f.svg' ||
12345
// external image
124-
name === 'struct-image-02-b' ||
46+
name === 'w3c-svg-11-test-suite/svg/struct-image-02-b.svg' ||
12547
// complex selectors are messed becase of converting shapes to paths
126-
name === 'struct-use-10-f' ||
127-
name === 'struct-use-11-f' ||
128-
name === 'styling-css-01-b' ||
129-
name === 'styling-css-03-b' ||
130-
name === 'styling-css-04-f' ||
48+
name === 'w3c-svg-11-test-suite/svg/struct-use-10-f.svg' ||
49+
name === 'w3c-svg-11-test-suite/svg/struct-use-11-f.svg' ||
50+
name === 'w3c-svg-11-test-suite/svg/styling-css-01-b.svg' ||
51+
name === 'w3c-svg-11-test-suite/svg/styling-css-03-b.svg' ||
52+
name === 'w3c-svg-11-test-suite/svg/styling-css-04-f.svg' ||
13153
// strange artifact breaks inconsistently breaks regression tests
132-
name === 'filters-conv-05-f'
54+
name === 'w3c-svg-11-test-suite/svg/filters-conv-05-f.svg'
13355
) {
13456
console.info(`${name} is skipped`);
13557
skipped += 1;
@@ -166,17 +88,19 @@ const runTests = async ({ svgFiles }) => {
16688
mismatched += 1;
16789
console.error(`${name} is mismatched`);
16890
if (process.env.NO_DIFF == null) {
169-
await fs.promises.mkdir('diffs', { recursive: true });
170-
await fs.promises.writeFile(
171-
`diffs/${name}.diff.png`,
172-
PNG.sync.write(diff)
91+
const file = path.join(
92+
__dirname,
93+
'regression-diffs',
94+
`${name}.diff.png`
17395
);
96+
await fs.promises.mkdir(path.dirname(file), { recursive: true });
97+
await fs.promises.writeFile(file, PNG.sync.write(diff));
17498
}
17599
}
176100
};
177101
const browser = await chromium.launch();
178102
const context = await browser.newContext({ javaScriptEnabled: false });
179-
const chunks = chunkInto(svgFiles, 8);
103+
const chunks = chunkInto(list, 8);
180104
await Promise.all(
181105
chunks.map(async (chunk) => {
182106
const page = await context.newPage();
@@ -193,18 +117,60 @@ const runTests = async ({ svgFiles }) => {
193117
return mismatched === 0;
194118
};
195119

120+
const readdirRecursive = async (absolute, relative = '') => {
121+
let result = [];
122+
const list = await fs.promises.readdir(absolute, { withFileTypes: true });
123+
for (const item of list) {
124+
const itemAbsolute = path.join(absolute, item.name);
125+
const itemRelative = path.join(relative, item.name);
126+
if (item.isDirectory()) {
127+
const itemList = await readdirRecursive(itemAbsolute, itemRelative);
128+
result = [...result, ...itemList];
129+
} else if (item.name.endsWith('.svg')) {
130+
result = [...result, itemRelative];
131+
}
132+
}
133+
return result;
134+
};
135+
196136
(async () => {
197137
try {
198138
const start = process.hrtime.bigint();
199-
console.info('Download W3C SVG 1.1 Test Suite and extract svg files');
200-
const svgFiles = await readSvgFiles();
201-
const optimizedFiles = optimizeSvgFiles(svgFiles);
139+
const fixturesDir = path.join(__dirname, 'regression-fixtures');
140+
const list = await readdirRecursive(fixturesDir);
141+
const originalFiles = new Map();
142+
const optimizedFiles = new Map();
143+
// read original and optimize
144+
let failed = 0;
145+
for (const name of list) {
146+
try {
147+
const file = path.join(fixturesDir, name);
148+
const original = await fs.promises.readFile(file, 'utf-8');
149+
const result = optimize(original, { path: name, floatPrecision: 4 });
150+
if (result.error) {
151+
console.error(result.error);
152+
console.error(`File: ${name}`);
153+
failed += 1;
154+
} else {
155+
originalFiles.set(name, original);
156+
optimizedFiles.set(name, result.data);
157+
}
158+
} catch (error) {
159+
console.error(error);
160+
console.error(`File: ${name}`);
161+
failed += 1;
162+
}
163+
}
164+
if (failed !== 0) {
165+
throw Error(`Failed to optimize ${failed} cases`);
166+
}
167+
// setup server
202168
const server = http.createServer((req, res) => {
203169
if (req.url.startsWith('/original/')) {
204170
const name = req.url.slice('/original/'.length);
205-
if (svgFiles.has(name)) {
171+
if (originalFiles.has(name)) {
206172
res.setHeader('Content-Type', 'image/svg+xml');
207-
res.end(svgFiles.get(name));
173+
res.end(originalFiles.get(name));
208174
return;
209175
}
210176
}
@@ -222,8 +188,9 @@ const runTests = async ({ svgFiles }) => {
222188
await new Promise((resolve) => {
223189
server.listen(5000, resolve);
224190
});
225-
const passed = await runTests({ svgFiles: Array.from(svgFiles.keys()) });
191+
const passed = await runTests({ list });
226192
server.close();
193+
// compute time
227194
const end = process.hrtime.bigint();
228195
const diff = (end - start) / BigInt(1e6);
229196
if (passed) {

0 commit comments

Comments
 (0)