WebVTT served via HLS never results in cues
[WebKit.git] / Websites / perf.webkit.org / server-tests / resources / test-server.js
1 'use strict';
2
3 let assert = require('assert');
4 let childProcess = require('child_process');
5 let fs = require('fs');
6 let path = require('path');
7
8 let Config = require('../../tools/js/config.js');
9 let Database = require('../../tools/js/database.js');
10 let RemoteAPI = require('../../tools/js/remote.js').RemoteAPI;
11
12 class TestServer {
13     constructor()
14     {
15         this._pidFile = null;
16         this._testConfigPath = Config.path('testServer.config');
17         this._dataDirectory = Config.path('dataDirectory');
18         this._backupDataPath = null;
19         this._pidWaitStart = null;
20         this._shouldLog = false;
21         this._pgsqlDirectory = null;
22         this._server = null;
23
24         this._databaseName = Config.value('testDatabaseName');
25         this._databaseUser = Config.value('database.username');
26         this._databaseHost = Config.value('database.host');
27         this._databasePort = Config.value('database.port');
28         this._database = null;
29
30         this._remote = null
31     }
32
33     start()
34     {
35         let testConfigContent = this.testConfig();
36         fs.writeFileSync(this._testConfigPath, JSON.stringify(testConfigContent, null, '    '));
37
38         this._ensureTestDatabase();
39         this._ensureDataDirectory();
40
41         return this._startApache();
42     }
43
44     stop()
45     {
46         this._restoreDataDirectory();
47
48         return this._stopApache();
49     }
50
51     remoteAPI()
52     {
53         assert(this._remote);
54         return this._remote;
55     }
56
57     database()
58     {
59         assert(this._databaseName);
60         if (!this._database)
61             this._database = new Database(this._databaseName);
62         return this._database;
63     }
64
65     testConfig()
66     {
67         return {
68             'siteTitle': 'Test Dashboard',
69             'debug': true,
70             'jsonCacheMaxAge': 600,
71             'dataDirectory': Config.value('dataDirectory'),
72             'database': {
73                 'host': Config.value('database.host'),
74                 'port': Config.value('database.port'),
75                 'username': Config.value('database.username'),
76                 'password': Config.value('database.password'),
77                 'name': Config.value('testDatabaseName'),
78             },
79             'uploadFileLimitInMB': 2,
80             'uploadUserQuotaInMB': 5,
81             'uploadTotalQuotaInMB': 6,
82             'uploadDirectory': Config.value('dataDirectory') + '/uploaded',
83             'universalSlavePassword': null,
84             'maintenanceMode': false,
85             'clusterStart': [2000, 1, 1, 0, 0],
86             'clusterSize': [0, 2, 0],
87             'defaultDashboard': [[]],
88             'dashboards': {},
89             'summaryPages': []
90         }
91     }
92
93     _ensureDataDirectory()
94     {
95         let backupPath = path.resolve(this._dataDirectory, '../original-data');
96         if (fs.existsSync(this._dataDirectory)) {
97             assert.ok(!fs.existsSync(backupPath), `Both ${this._dataDirectory} and ${backupPath} exist. Cannot make a backup of data`);
98             fs.renameSync(this._dataDirectory, backupPath);
99             this._backupDataPath = backupPath;
100         } else if (fs.existsSync(backupPath)) // Assume this is a backup from the last failed run
101             this._backupDataPath = backupPath;
102         fs.mkdirSync(this._dataDirectory, 0o755);
103         fs.mkdirSync(path.resolve(this._dataDirectory, 'uploaded'), 0o755);
104     }
105
106     _restoreDataDirectory()
107     {
108         childProcess.execFileSync('rm', ['-rf', this._dataDirectory]);
109         if (this._backupDataPath)
110             fs.rename(this._backupDataPath, this._dataDirectory);
111     }
112
113     cleanDataDirectory()
114     {
115         let fileList = fs.readdirSync(this._dataDirectory);
116         for (let filename of fileList) {
117             if (filename != 'uploaded')
118                 fs.unlinkSync(path.resolve(this._dataDirectory, filename));
119         }
120         fileList = fs.readdirSync(path.resolve(this._dataDirectory, 'uploaded'));
121         for (let filename of fileList)
122             fs.unlinkSync(path.resolve(this._dataDirectory, 'uploaded', filename));
123     }
124
125     _ensureTestDatabase()
126     {
127         try {
128             this._executePgsqlCommand('dropdb');
129         } catch (error) { }
130         this._executePgsqlCommand('createdb');
131         this._executePgsqlCommand('psql', ['--command', `grant all privileges on database "${this._databaseName}" to "${this._databaseUser}";`]);
132         this.initDatabase();
133     }
134
135     initDatabase()
136     {
137         if (this._database)
138             this._database.disconnect();
139         this._database = null;
140
141         let initFilePath = Config.pathFromRoot('init-database.sql');
142         this._executePgsqlCommand('psql', ['--username', this._databaseUser, '--file', initFilePath],
143             {stdio: ['ignore', 'ignore', 'ignore']});
144     }
145
146     _executePgsqlCommand(command, args, options)
147     {
148         if (!this._pgsqlDirectory)
149             this._pgsqlDirectory = this._determinePgsqlDirectory();
150         childProcess.execFileSync(path.resolve(this._pgsqlDirectory, command),
151             [this._databaseName, '--host', this._databaseHost, '--port', this._databasePort].concat(args || []), options);
152     }
153
154     _determinePgsqlDirectory()
155     {
156         try {
157             let initdbLocation = childProcess.execFileSync('which', ['initdb']);
158             return path.dirname(initdbLocation);
159         } catch (error) {
160             let serverPgsqlLocation = '/Applications/Server.app/Contents/ServerRoot/usr/bin/';
161             childProcess.execFileSync(path.resolve(serverPgsqlLocation, 'initdb'), ['--version']);
162             return serverPgsqlLocation;
163         }
164     }
165
166     _startApache()
167     {
168         let pidFile = Config.path('testServer.httpdPID');
169         let httpdConfig = Config.path('testServer.httpdConfig');
170         let port = Config.value('testServer.port');
171         let errorLog = Config.path('testServer.httpdErrorLog');
172         let mutexFile = Config.path('testServer.httpdMutexDir');
173         let phpVersion = childProcess.execFileSync('php', ['-v'], {stdio: ['pipe', 'pipe', 'ignore']}).toString().includes('PHP 5') ? 'PHP5' : 'PHP7';
174
175         if (!fs.existsSync(mutexFile))
176             fs.mkdirSync(mutexFile, 0o755);
177
178         let args = [
179             '-f', httpdConfig,
180             '-c', `SetEnv ORG_WEBKIT_PERF_CONFIG_PATH ${this._testConfigPath}`,
181             '-c', `Listen ${port}`,
182             '-c', `PidFile ${pidFile}`,
183             '-c', `ErrorLog ${errorLog}`,
184             '-c', `Mutex file:${mutexFile}`,
185             '-c', `DocumentRoot ${Config.serverRoot()}`,
186             '-D', phpVersion];
187
188         if (this._shouldLog)
189             console.log(args);
190
191         childProcess.execFileSync('httpd', args);
192
193         this._server = {
194             scheme: 'http',
195             host: 'localhost',
196             port: port,
197         };
198         this._pidWaitStart = Date.now();
199         this._pidFile = pidFile;
200
201         this._remote = new RemoteAPI(this._server);
202
203         return new Promise(this._waitForPid.bind(this, true));
204     }
205
206     _stopApache()
207     {
208         if (!this._pidFile)
209             return;
210
211         let pid = fs.readFileSync(this._pidFile, 'utf-8').trim();
212
213         if (this._shouldLog)
214             console.log('Stopping', pid);
215
216         childProcess.execFileSync('kill', ['-TERM', pid]);
217
218         this._pidWaitStart = Date.now();
219         return new Promise(this._waitForPid.bind(this, false));
220     }
221
222     _waitForPid(shouldExist, resolve, reject)
223     {
224         if (fs.existsSync(this._pidFile) != shouldExist) {
225             if (Date.now() - this._pidWaitStart > 8000)
226                 reject();
227             else
228                 setTimeout(this._waitForPid.bind(this, shouldExist, resolve, reject), 100);
229             return;
230         }
231         resolve();
232     }
233
234     inject()
235     {
236         let self = this;
237         before(function () {
238             this.timeout(10000);
239             return self.start();
240         });
241
242         let originalRemote;
243
244         beforeEach(function () {
245             this.timeout(10000);
246             self.initDatabase();
247             self.cleanDataDirectory();
248             originalRemote = global.RemoteAPI;
249             global.RemoteAPI = self._remote;
250             self._remote.clearCookies();
251
252             if (global.PrivilegedAPI) {
253                 global.PrivilegedAPI._token = null;
254                 global.PrivilegedAPI._expiration = null;
255             }
256         });
257
258         after(function () {
259             this.timeout(10000);
260             global.RemoteAPI = originalRemote;
261             return self.stop();
262         });
263     }
264 }
265
266 if (!global.TestServer)
267     global.TestServer = new TestServer;
268
269 if (typeof module != 'undefined')
270     module.exports = global.TestServer;