Change from pnpm to npm, add ./link.sh shortcut for npm style package linking
[html-minifier.git] / backtest.js
1 #!/usr/bin/env node
2
3 'use strict';
4
5 var child_process = require('child_process'),
6     fs = require('fs'),
7     os = require('os'),
8     path = require('path'),
9     Progress = require('progress');
10
11 var urls = require('./benchmarks');
12 var fileNames = Object.keys(urls);
13
14 function git() {
15   var args = [].concat.apply([], [].slice.call(arguments, 0, -1));
16   var callback = arguments[arguments.length - 1];
17   var task = child_process.spawn('git', args, { stdio: ['ignore', 'pipe', 'ignore'] });
18   var output = '';
19   task.stdout.setEncoding('utf8');
20   task.stdout.on('data', function(data) {
21     output += data;
22   });
23   task.on('exit', function(code) {
24     callback(code, output);
25   });
26 }
27
28 function readText(filePath, callback) {
29   fs.readFile(filePath, { encoding: 'utf8' }, callback);
30 }
31
32 function writeText(filePath, data) {
33   fs.writeFile(filePath, data, { encoding: 'utf8' }, function(err) {
34     if (err) {
35       throw err;
36     }
37   });
38 }
39
40 function loadModule() {
41   require('./src/htmlparser');
42   return require('./src/htmlminifier').minify || global.minify;
43 }
44
45 function getOptions(fileName, options) {
46   var result = {
47     minifyURLs: {
48       site: urls[fileName]
49     }
50   };
51   for (var key in options) {
52     result[key] = options[key];
53   }
54   return result;
55 }
56
57 function minify(hash, options) {
58   var minify = loadModule();
59   process.send('ready');
60   var count = fileNames.length;
61   fileNames.forEach(function(fileName) {
62     readText(path.join('benchmarks/', fileName + '.html'), function(err, data) {
63       if (err) {
64         throw err;
65       }
66       else {
67         try {
68           var minified = minify(data, getOptions(fileName, options));
69           if (minified) {
70             process.send({ name: fileName, size: minified.length });
71           }
72           else {
73             throw new Error('unexpected result: ' + minified);
74           }
75         }
76         catch (e) {
77           console.error('[' + fileName + ']', e.stack || e);
78         }
79         finally {
80           if (!--count) {
81             process.disconnect();
82           }
83         }
84       }
85     });
86   });
87 }
88
89 function print(table) {
90   var output = [];
91   var errors = [];
92   var row = fileNames.slice(0);
93   row.unshift('hash', 'date');
94   output.push(row.join(','));
95   for (var hash in table) {
96     var data = table[hash];
97     row = [hash, '"' + data.date + '"'];
98     fileNames.forEach(function(fileName) {
99       row.push(data[fileName]);
100     });
101     output.push(row.join(','));
102     if (data.error) {
103       errors.push(hash + ' - ' + data.error);
104     }
105   }
106   writeText('backtest.csv', output.join('\n'));
107   writeText('backtest.log', errors.join('\n'));
108 }
109
110 if (process.argv.length > 2) {
111   var count = +process.argv[2];
112   if (count) {
113     git('log', '--date=iso', '--pretty=format:%h %cd', '-' + count, function(code, data) {
114       var table = {};
115       var commits = data.split(/\s*?\n/).map(function(line) {
116         var index = line.indexOf(' ');
117         var hash = line.substr(0, index);
118         table[hash] = {
119           date: line.substr(index + 1).replace('+', '').replace(/ 0000$/, '')
120         };
121         return hash;
122       });
123       var nThreads = os.cpus().length;
124       var running = 0;
125       var progress = new Progress('[:bar] :etas', {
126         width: 50,
127         total: commits.length * 2
128       });
129
130       function fork() {
131         if (commits.length && running < nThreads) {
132           var hash = commits.shift();
133           var task = child_process.fork('./backtest', { silent: true });
134           var error = '';
135           var id = setTimeout(function() {
136             if (task.connected) {
137               error += 'task timed out\n';
138               task.kill();
139             }
140           }, 60000);
141           task.on('message', function(data) {
142             if (data === 'ready') {
143               progress.tick(1);
144               fork();
145             }
146             else {
147               table[hash][data.name] = data.size;
148             }
149           }).on('exit', function() {
150             progress.tick(1);
151             clearTimeout(id);
152             if (error) {
153               table[hash].error = error;
154             }
155             if (!--running && !commits.length) {
156               print(table);
157             }
158             else {
159               fork();
160             }
161           });
162           task.stderr.setEncoding('utf8');
163           task.stderr.on('data', function(data) {
164             error += data;
165           });
166           task.stdout.resume();
167           task.send(hash);
168           running++;
169         }
170       }
171
172       fork();
173     });
174   }
175   else {
176     console.error('Invalid input:', process.argv[2]);
177   }
178 }
179 else {
180   process.on('message', function(hash) {
181     var paths = ['src', 'benchmark.conf', 'sample-cli-config-file.conf'];
182     git('reset', 'HEAD', '--', paths, function() {
183       var conf = 'sample-cli-config-file.conf';
184
185       function checkout() {
186         var path = paths.shift();
187         git('checkout', hash, '--', path, function(code) {
188           if (code === 0 && path === 'benchmark.conf') {
189             conf = path;
190           }
191           if (paths.length) {
192             checkout();
193           }
194           else {
195             readText(conf, function(err, data) {
196               if (err) {
197                 throw err;
198               }
199               else {
200                 minify(hash, JSON.parse(data));
201               }
202             });
203           }
204         });
205       }
206
207       checkout();
208     });
209   });
210 }