2
2
3
3
const fs = require ( 'fs' ) ;
4
4
const path = require ( 'path' ) ;
5
- const util = require ( 'util' ) ;
6
- const zlib = require ( 'zlib' ) ;
7
5
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' ) ;
12
6
const { chromium } = require ( 'playwright' ) ;
13
7
const { PNG } = require ( 'pngjs' ) ;
14
8
const pixelmatch = require ( 'pixelmatch' ) ;
15
9
const { optimize } = require ( '../lib/svgo.js' ) ;
16
10
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
-
91
11
const chunkInto = ( array , chunksCount ) => {
92
12
// take upper bound to include tail
93
13
const chunkSize = Math . ceil ( array . length / chunksCount ) ;
@@ -99,37 +19,39 @@ const chunkInto = (array, chunksCount) => {
99
19
return result ;
100
20
} ;
101
21
102
- const runTests = async ( { svgFiles } ) => {
22
+ const runTests = async ( { list } ) => {
103
23
let skipped = 0 ;
104
24
let mismatched = 0 ;
105
25
let passed = 0 ;
106
26
console . info ( 'Start browser...' ) ;
107
27
const processFile = async ( page , name ) => {
108
28
if (
109
29
// hard to detect the end of animation
110
- name . startsWith ( 'animate-' ) ||
30
+ name . startsWith ( 'w3c-svg-11-test-suite/svg/ animate-' ) ||
111
31
// 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 ' ||
113
33
// messed gradients
114
- name === 'pservers-grad-18-b' ||
34
+ name === 'w3c-svg-11-test-suite/svg/ pservers-grad-18-b.svg ' ||
115
35
// 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' ||
117
39
// 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 ' ||
119
41
// 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 ' ||
121
43
// 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 ' ||
123
45
// external image
124
- name === 'struct-image-02-b' ||
46
+ name === 'w3c-svg-11-test-suite/svg/ struct-image-02-b.svg ' ||
125
47
// 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 ' ||
131
53
// 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 '
133
55
) {
134
56
console . info ( `${ name } is skipped` ) ;
135
57
skipped += 1 ;
@@ -166,17 +88,19 @@ const runTests = async ({ svgFiles }) => {
166
88
mismatched += 1 ;
167
89
console . error ( `${ name } is mismatched` ) ;
168
90
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`
173
95
) ;
96
+ await fs . promises . mkdir ( path . dirname ( file ) , { recursive : true } ) ;
97
+ await fs . promises . writeFile ( file , PNG . sync . write ( diff ) ) ;
174
98
}
175
99
}
176
100
} ;
177
101
const browser = await chromium . launch ( ) ;
178
102
const context = await browser . newContext ( { javaScriptEnabled : false } ) ;
179
- const chunks = chunkInto ( svgFiles , 8 ) ;
103
+ const chunks = chunkInto ( list , 8 ) ;
180
104
await Promise . all (
181
105
chunks . map ( async ( chunk ) => {
182
106
const page = await context . newPage ( ) ;
@@ -193,18 +117,60 @@ const runTests = async ({ svgFiles }) => {
193
117
return mismatched === 0 ;
194
118
} ;
195
119
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
+
196
136
( async ( ) => {
197
137
try {
198
138
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
202
168
const server = http . createServer ( ( req , res ) => {
203
169
if ( req . url . startsWith ( '/original/' ) ) {
204
170
const name = req . url . slice ( '/original/' . length ) ;
205
- if ( svgFiles . has ( name ) ) {
171
+ if ( originalFiles . has ( name ) ) {
206
172
res . setHeader ( 'Content-Type' , 'image/svg+xml' ) ;
207
- res . end ( svgFiles . get ( name ) ) ;
173
+ res . end ( originalFiles . get ( name ) ) ;
208
174
return ;
209
175
}
210
176
}
@@ -222,8 +188,9 @@ const runTests = async ({ svgFiles }) => {
222
188
await new Promise ( ( resolve ) => {
223
189
server . listen ( 5000 , resolve ) ;
224
190
} ) ;
225
- const passed = await runTests ( { svgFiles : Array . from ( svgFiles . keys ( ) ) } ) ;
191
+ const passed = await runTests ( { list } ) ;
226
192
server . close ( ) ;
193
+ // compute time
227
194
const end = process . hrtime . bigint ( ) ;
228
195
const diff = ( end - start ) / BigInt ( 1e6 ) ;
229
196
if ( passed ) {
0 commit comments