This is an automated email from the ASF dual-hosted git repository. shenyi pushed a commit to branch test-autorun in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
The following commit(s) were added to refs/heads/test-autorun by this push: new 54ed04f test: support multi threads 54ed04f is described below commit 54ed04ffa16071165b52205898995f9ffff85c38 Author: pissang <bm2736...@gmail.com> AuthorDate: Sun Sep 8 15:05:10 2019 +0800 test: support multi threads --- test/runTest/client/client.css | 21 ++++ test/runTest/client/client.js | 241 ++++++++++++++++++++++------------------- test/runTest/client/index.html | 12 +- test/runTest/server.js | 102 +++++++++++++---- 4 files changed, 242 insertions(+), 134 deletions(-) diff --git a/test/runTest/client/client.css b/test/runTest/client/client.css index 05d3ed9..1b2b025 100644 --- a/test/runTest/client/client.css +++ b/test/runTest/client/client.css @@ -47,6 +47,27 @@ .nav-toolbar .controls { margin-top: 10px; } +.nav-toolbar .controls>* { + display: inline-block; + vertical-align: middle; +} +.nav-toolbar .controls .el-checkbox { + margin-right: 2px; +} +.nav-toolbar .el-icon-setting { + color: #f3f3f3; + font-size: 20px; + margin-left: 5px; +} +.run-config-item { + margin: 5px 0; +} +.run-config-item>* { + display: inline-block; + vertical-align: middle; + margin-right: 10px; +} + .test-list { overflow-x: hidden; diff --git a/test/runTest/client/client.js b/test/runTest/client/client.js index 38f4a75..6ac28a1 100644 --- a/test/runTest/client/client.js +++ b/test/runTest/client/client.js @@ -32,121 +32,141 @@ function processTestsData(tests, oldTestsData) { return tests; } -socket.on('connect', () => { - console.log('Connected'); - const app = new Vue({ - el: '#app', - data: { - fullTests: [], - currentTestName: '', - sortBy: 'name', - searchString: '', - running: false, - - allSelected: false, - lastSelectedIndex: -1, - +const app = new Vue({ + el: '#app', + data: { + fullTests: [], + currentTestName: '', + sortBy: 'name', + searchString: '', + running: false, + + allSelected: false, + lastSelectedIndex: -1, + + runConfig: { noHeadless: false, - }, - computed: { - tests() { - let sortFunc = this.sortBy === 'name' - ? (a, b) => a.name.localeCompare(b.name) - : (a, b) => { - if (a.percentage === b.percentage) { - return a.name.localeCompare(b.name); - } - return a.percentage - b.percentage; - }; - - if (!this.searchString) { - // Not modify the original tests data. - return this.fullTests.slice().sort(sortFunc); - } + threads: 1 + } + }, + computed: { + tests() { + let sortFunc = this.sortBy === 'name' + ? (a, b) => a.name.localeCompare(b.name) + : (a, b) => { + if (a.percentage === b.percentage) { + return a.name.localeCompare(b.name); + } + return a.percentage - b.percentage; + }; - return this.fullTests.filter(test => { - return test.name.match(this.searchString); - }).sort(sortFunc); - }, + if (!this.searchString) { + // Not modify the original tests data. + return this.fullTests.slice().sort(sortFunc); + } - currentTest() { - let currentTest = this.fullTests.find(item => item.name === this.currentTestName); - if (!currentTest) { - currentTest = this.fullTests[0]; - } - return currentTest; - }, + return this.fullTests.filter(test => { + return test.name.match(this.searchString); + }).sort(sortFunc); + }, - currentTestUrl() { - return window.location.origin + '/test/' + this.currentTestName + '.html'; - }, + currentTest() { + let currentTest = this.fullTests.find(item => item.name === this.currentTestName); + if (!currentTest) { + currentTest = this.fullTests[0]; + } + return currentTest; + }, - currentTestRecordUrl() { - return window.location.origin + '/test/runTest/recorder/index.html#' + this.currentTestName; - }, + currentTestUrl() { + return window.location.origin + '/test/' + this.currentTestName + '.html'; + }, - isSelectAllIndeterminate: { - get() { - if (!this.tests.length) { - return true; - } - return this.tests.some(test => { - return test.selected !== this.tests[0].selected; - }); - }, - set() {} - } + currentTestRecordUrl() { + return window.location.origin + '/test/runTest/recorder/index.html#' + this.currentTestName; }, - methods: { - goto(url) { - window.location.hash = '#' + url; - }, - toggleSort() { - this.sortBy = this.sortBy === 'name' ? 'percentage' : 'name'; - }, - handleSelectAllChange(val) { - // Only select filtered tests. - this.tests.forEach(test => { - test.selected = val; - }); - this.isSelectAllIndeterminate = false; - }, - handleSelect(idx) { - Vue.nextTick(() => { - this.lastSelectedIndex = idx; - }); - }, - handleShiftSelect(idx) { - if (this.lastSelectedIndex < 0) { - return; - } - let start = Math.min(this.lastSelectedIndex, idx); - let end = Math.max(this.lastSelectedIndex, idx); - let selected = !this.tests[idx].selected; // Will change - for (let i = start; i < end; i++) { - this.tests[i].selected = selected; - } - }, - refreshList() { - }, - runSelectedTests() { - const tests = this.fullTests.filter(test => { - return test.selected; - }).map(test => { - return test.name; - }); - if (tests.length > 0) { - this.running = true; - socket.emit('run', {tests, noHeadless: this.noHeadless}); + isSelectAllIndeterminate: { + get() { + if (!this.tests.length) { + return true; } + return this.tests.some(test => { + return test.selected !== this.tests[0].selected; + }); }, - stopTests() { - this.running = false; - socket.emit('stop'); + set() {} + } + }, + methods: { + goto(url) { + window.location.hash = '#' + url; + }, + toggleSort() { + this.sortBy = this.sortBy === 'name' ? 'percentage' : 'name'; + }, + handleSelectAllChange(val) { + // Only select filtered tests. + this.tests.forEach(test => { + test.selected = val; + }); + this.isSelectAllIndeterminate = false; + }, + handleSelect(idx) { + Vue.nextTick(() => { + this.lastSelectedIndex = idx; + }); + }, + handleShiftSelect(idx) { + if (this.lastSelectedIndex < 0) { + return; } + let start = Math.min(this.lastSelectedIndex, idx); + let end = Math.max(this.lastSelectedIndex, idx); + let selected = !this.tests[idx].selected; // Will change + for (let i = start; i < end; i++) { + this.tests[i].selected = selected; + } + }, + refreshList() { + + }, + runSelectedTests() { + const tests = this.fullTests.filter(test => { + return test.selected; + }).map(test => { + return test.name; + }); + runTests(tests); + }, + stopTests() { + this.running = false; + socket.emit('stop'); } - }); + } +}); + +function runTests(tests) { + if (tests.length > 0) { + app.running = true; + socket.emit('run', { + tests, + threads: app.runConfig.threads, + noHeadless: app.runConfig.noHeadless + }); + } + else { + app.$notify({ + title: 'No test selected.', + position: 'bottom-right' + }); + } +} + + +socket.on('connect', () => { + console.log('Connected'); + app.$el.style.display = 'block'; let firstUpdate = true; @@ -159,11 +179,7 @@ socket.on('connect', () => { dangerouslyUseHTMLString: true, center: true }).then(value => { - app.running = true; - socket.emit('run', { - tests: msg.tests.map(test => test.name), - noHeadless: this.noHeadless - }); + runTests(msg.tests.map(test => test.name)); }).catch(() => {}); } // TODO @@ -172,10 +188,13 @@ socket.on('connect', () => { firstUpdate = false; }); - socket.on('finish', () => { + socket.on('finish', res => { app.$notify({ - title: 'Test Complete', - position: 'bottom-right' + type: 'success', + title: `${res.count} test complete`, + message: `Cost: ${(res.time / 1000).toFixed(1)} s. Threads: ${res.threads}`, + position: 'top-right', + duration: 8000 }); app.running = false; }); diff --git a/test/runTest/client/index.html b/test/runTest/client/index.html index 7f0ccd0..aad6d83 100644 --- a/test/runTest/client/index.html +++ b/test/runTest/client/index.html @@ -29,7 +29,17 @@ <el-button v-if="running" title="Run Selected" @click="stopTests" circle size="mini" type="primary" icon="el-icon-close"></el-button> </el-button-group> - <el-checkbox v-model="noHeadless" label="Playback"></el-checkbox> + + <el-popover title="Configuration" class="run-configuration"> + <div class="config-item"> + <el-checkbox v-model="runConfig.noHeadless">Replay</el-checkbox> + </div> + <div class="run-config-item"> + <span>Threads</span> + <el-slider style="width: 140px;" v-model="runConfig.threads" :step="1" :min="1" :max="8" show-stops></el-slider> + </div> + <i slot="reference" class="el-icon-setting"></i> + </el-popover> <!-- <el-button-group> diff --git a/test/runTest/server.js b/test/runTest/server.js index 05472a3..1cf628c 100644 --- a/test/runTest/server.js +++ b/test/runTest/server.js @@ -30,13 +30,13 @@ function serve() { }; }; -let testProcess; +let runningThreads = []; let pendingTests; function stopRunningTests() { - if (testProcess) { - testProcess.kill(); - testProcess = null; + if (runningThreads) { + runningThreads.forEach(thread => thread.kill()); + runningThreads = []; } if (pendingTests) { pendingTests.forEach(testOpt => { @@ -48,9 +48,48 @@ function stopRunningTests() { } } -function startTests(testsNameList, socket, noHeadless) { +class Thread { + constructor() { + this.tests = []; + + this.onExit; + this.onUpdate; + } + + fork(noHeadless) { + let p = fork(path.join(__dirname, 'cli.js'), [ + '--tests', + this.tests.map(testOpt => testOpt.name).join(','), + '--speed', + 5, + ...(noHeadless ? ['--no-headless'] : []) + ]); + this.p = p; + + // Finished one test + p.on('message', testOpt => { + mergeTestsResults([testOpt]); + saveTestsList(); + this.onUpdate(); + }); + // Finished all + p.on('exit', () => { + this.p = null; + setTimeout(this.onExit); + }); + } + + kill() { + if (this.p) { + this.p.kill(); + } + } +} + +function startTests(testsNameList, socket, {noHeadless, threadsCount}) { console.log(testsNameList.join(',')); + threadsCount = threadsCount || 1; stopRunningTests(); return new Promise(resolve => { @@ -65,24 +104,34 @@ function startTests(testsNameList, socket, noHeadless) { socket.emit('update', {tests: getTestsList()}); - testProcess = fork(path.join(__dirname, 'cli.js'), [ - '--tests', - pendingTests.map(testOpt => testOpt.name).join(','), - '--speed', - 5, - ...(noHeadless ? ['--no-headless'] : []) - ]); - // Finished one test - testProcess.on('message', testOpt => { - mergeTestsResults([testOpt]); + let runningCount = 0; + function onExit() { + runningCount--; + if (runningCount === 0) { + resolve(); + } + } + function onUpdate() { // Merge tests. socket.emit('update', {tests: getTestsList(), running: true}); - saveTestsList(); - }); - // Finished all - testProcess.on('exit', () => { + } + + threadsCount = Math.min(threadsCount, pendingTests.length); + // Assigning tests to threads + runningThreads = new Array(threadsCount).fill(0).map(a => new Thread() ); + for (let i = 0; i < pendingTests.length; i++) { + runningThreads[i % threadsCount].tests.push(pendingTests[i]); + } + for (let i = 0; i < threadsCount; i++) { + runningThreads[i].onExit = onExit; + runningThreads[i].onUpdate = onUpdate; + runningThreads[i].fork(noHeadless, onExit); + runningCount++; + } + // If something bad happens and no proccess are started successfully + if (runningCount === 0) { resolve(); - }); + } }); } @@ -118,13 +167,22 @@ async function start() { socket.emit('update', {tests: getTestsList()}); socket.on('run', async data => { + let startTime = Date.now(); // TODO Should broadcast to all sockets. try { - await startTests(data.tests, socket, data.noHeadless); + await startTests( + data.tests, + socket, + { noHeadless: data.noHeadless, threadsCount: data.threads } + ); } catch (e) { console.error(e); } console.log('Finished'); - socket.emit('finish'); + socket.emit('finish', { + time: Date.now() - startTime, + count: data.tests.length, + threads: data.threads + }); }); socket.on('stop', () => { stopRunningTests(); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org