benchmark runner

This commit is contained in:
Thomas Aylott 2013-12-17 00:25:05 -05:00
parent 605b42e622
commit 91821007ed
32 changed files with 1568 additions and 182 deletions

View File

@ -26,6 +26,7 @@ env:
matrix:
- TEST_TYPE=test:full
- TEST_TYPE=lint
- TEST_TYPE=perf:full
- TEST_TYPE=test:coverage
- TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11
- TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10
@ -43,6 +44,7 @@ matrix:
allow_failures:
- env: TEST_TYPE=lint
- env: TEST_TYPE=test:coverage
- env: TEST_TYPE=perf:full
- env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11
- env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10
- env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie9

View File

@ -21,7 +21,8 @@ module.exports = function(grunt) {
browserify: require('./grunt/config/browserify'),
populist: require('./grunt/config/populist'),
connect: require('./grunt/config/server')(grunt),
"webdriver-jasmine": require('./grunt/config/webdriver-jasmine.js'),
"webdriver-jasmine": require('./grunt/config/webdriver-jasmine'),
"webdriver-perf": require('./grunt/config/webdriver-perf'),
npm: require('./grunt/config/npm'),
clean: ['./build', './*.gem', './docs/_site', './examples/shared/*.js', '.module-cache'],
jshint: require('./grunt/config/jshint'),
@ -39,6 +40,8 @@ module.exports = function(grunt) {
// Alias 'jshint' to 'lint' to better match the workflow we know
grunt.registerTask('lint', ['jshint']);
grunt.registerTask('download-previous-version', require('./grunt/tasks/download-previous-version.js'));
// Register jsx:debug and :release tasks.
grunt.registerMultiTask('jsx', jsxTask);
@ -51,6 +54,8 @@ module.exports = function(grunt) {
grunt.registerMultiTask('webdriver-jasmine', webdriverJasmineTasks);
grunt.registerMultiTask('webdriver-perf', require('./grunt/tasks/webdriver-perf'));
grunt.registerMultiTask('npm', npmTask);
grunt.registerTask('npm-react:release', npmReactTasks.buildRelease);
@ -67,6 +72,14 @@ module.exports = function(grunt) {
'version-check',
'browserify:withCodeCoverageLogging'
]);
grunt.registerTask('build:perf', [
'jsx:release',
'version-check',
'browserify:transformer',
'browserify:basic',
'browserify:min',
'download-previous-version'
]);
grunt.registerTask('build:test', [
'jsx:test',
'version-check',
@ -84,6 +97,12 @@ module.exports = function(grunt) {
'webdriver-jasmine:local'
]);
grunt.registerTask('perf:webdriver:phantomjs', [
'connect',
'webdriver-phantomjs',
'webdriver-perf:local'
]);
grunt.registerTask('test:full', [
'build:test',
'build:basic',
@ -98,6 +117,20 @@ module.exports = function(grunt) {
'webdriver-jasmine:saucelabs_chrome'
]);
grunt.registerTask('perf:full', [
'build:perf',
'connect',
'webdriver-phantomjs',
'webdriver-perf:local',
'sauce-tunnel',
'webdriver-perf:saucelabs_firefox',
'webdriver-perf:saucelabs_chrome',
'webdriver-perf:saucelabs_ie11',
'webdriver-perf:saucelabs_ie8',
]);
grunt.registerTask('test:webdriver:saucelabs', [
'build:test',
'build:basic',
@ -137,6 +170,7 @@ module.exports = function(grunt) {
'coverage:parse'
]);
grunt.registerTask('test', ['build:test', 'build:basic', 'test:webdriver:phantomjs']);
grunt.registerTask('perf', ['build:perf', 'perf:webdriver:phantomjs']);
grunt.registerTask('npm:test', ['build', 'npm:pack']);
// Optimized build task that does all of our builds. The subtasks will be run

View File

@ -44,6 +44,8 @@ module.exports = function(grunt){
coverageWriteStream.write(log.message + '\n');
} else if (log.type == 'coverage done') {
grunt.task.run('finalize-coverage-stream');
} else if (log.type == 'perf') {
grunt.event.emit('perf results', log.message);
} else {
grunt.verbose.writeln(log);
}

View File

@ -0,0 +1,113 @@
'use strict';
var grunt = require('grunt');
module.exports = function(props){
if (typeof props.url != 'string') {
throw TypeError('expected url string');
}
if ('isDoneTimeout' in props && typeof props.isDoneTimeout != 'number') {
throw TypeError('expected isDoneTimeout to be a number');
}
if ('onStart' in props && typeof props.onStart != 'function') {
throw TypeError('expected onStart to be a function');
}
if ('onComplete' in props && typeof props.onComplete != 'function') {
throw TypeError('expected onComplete to be a function');
}
if ('onError' in props && typeof props.onError != 'function') {
throw TypeError('expected onError to be a function');
}
var exports = {};
exports.local = {
webdriver: {
remote: { protocol: 'http:', hostname: '127.0.0.1', port: 9515, path: '/' }
},
url: props.url,
onStart: props.onStart,
onComplete: props.onComplete,
onError: props.onError,
isDoneTimeout: props.isDoneTimeout
};
if (grunt.option('debug')) {
exports.local.url += (exports.local.url.indexOf('?') == -1 ? '?' : '&') + 'debug=' + grunt.option('debug');
}
exports.saucelabs = {
webdriver: {
remote: {
/* https://github.com/admc/wd/blob/master/README.md#named-parameters */
user: process.env.SAUCE_USERNAME || 'React',
pwd: process.env.SAUCE_ACCESS_KEY || '339d32ca-d594-4570-a3c2-94c50a91919b',
protocol: 'http:',
hostname: 'ondemand.saucelabs.com',
port: '80',
path: '/wd/hub'
}
},
desiredCapabilities: {
"build": process.env.TRAVIS_BUILD_NUMBER || 'dev' + Date.now(),
"tunnel-identifier": process.env.TRAVIS_JOB_NUMBER || 'my awesome tunnel',
"browserName": "chrome"
},
url: exports.local.url,
onStart: function(browser){
grunt.log.writeln("Starting WebDriver Test. Watch results here: http://saucelabs.com/tests/" + browser.sessionID);
if (props.onStart) {
return props.onStart(browser);
}
},
onComplete: exports.local.onComplete,
onError: exports.local.onError,
isDoneTimeout: exports.local.isDoneTimeout
};
/* https://saucelabs.com/platforms */
exports.saucelabs_ios =
exports.saucelabs_ios7 = sauceItUp({ browserName: 'iphone', version: '7', platform:'OS X 10.9' });
exports.saucelabs_ios6_1 = sauceItUp({ browserName: 'iphone', version: '6.1', platform:'OS X 10.8' });
exports.saucelabs_ios6 = sauceItUp({ browserName: 'iphone', version: '6', platform:'OS X 10.8' });
exports.saucelabs_ios5_1 = sauceItUp({ browserName: 'iphone', version: '5.1', platform:'OS X 10.8' });
exports.saucelabs_ios5 = sauceItUp({ browserName: 'iphone', version: '5', platform:'OS X 10.6' });
exports.saucelabs_ios4 = sauceItUp({ browserName: 'iphone', version: '4', platform:'OS X 10.6' });
exports.saucelabs_ipad =
exports.saucelabs_ipad7 = sauceItUp({ browserName: 'ipad', version: '7', platform:'OS X 10.9' });
exports.saucelabs_ipad6_1 = sauceItUp({ browserName: 'ipad', version: '6.1', platform:'OS X 10.8' });
exports.saucelabs_ipad6 = sauceItUp({ browserName: 'ipad', version: '6', platform:'OS X 10.8' });
exports.saucelabs_ipad5_1 = sauceItUp({ browserName: 'ipad', version: '5.1', platform:'OS X 10.8' });
exports.saucelabs_ipad5 = sauceItUp({ browserName: 'ipad', version: '5', platform:'OS X 10.6' });
exports.saucelabs_ipad4 = sauceItUp({ browserName: 'ipad', version: '4', platform:'OS X 10.6' });
exports.saucelabs_android = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux' });
exports.saucelabs_android_tablet = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux', 'device-type':'tablet' });
exports.saucelabs_safari = sauceItUp({ browserName: 'safari' });
exports.saucelabs_chrome = sauceItUp({ browserName: 'chrome' });
exports.saucelabs_firefox = sauceItUp({ browserName: 'firefox' });
exports.saucelabs_ie =
exports.saucelabs_ie8 = sauceItUp({ browserName: 'internet explorer', version: 8 });
exports.saucelabs_ie9 = sauceItUp({ browserName: 'internet explorer', version: 9 });
exports.saucelabs_ie10 = sauceItUp({ browserName: 'internet explorer', version: 10 });
exports.saucelabs_ie11 = sauceItUp({ browserName: 'internet explorer', version: 11, platform:'Windows 8.1' });
function sauceItUp(desiredCapabilities) {
desiredCapabilities["build"] = exports.saucelabs.desiredCapabilities["build"];
desiredCapabilities["tunnel-identifier"] = exports.saucelabs.desiredCapabilities["tunnel-identifier"];
return {
webdriver: exports.saucelabs.webdriver,
url: exports.saucelabs.url,
onStart: exports.saucelabs.onStart,
onComplete: exports.saucelabs.onComplete,
onError: exports.saucelabs.onError,
isDoneTimeout: exports.saucelabs.isDoneTimeout,
desiredCapabilities: desiredCapabilities,
};
}
return exports;
}

View File

@ -2,11 +2,7 @@
var grunt = require('grunt');
exports.local = {
webdriver: {
remote: { protocol: 'http:', hostname: '127.0.0.1', port: 9515, path: '/' }
},
module.exports = require('./webdriver-all')({
url: "http://127.0.0.1:9999/test/index.html",
onComplete: function(passed){
if (!passed){
@ -16,76 +12,4 @@ exports.local = {
onError: function(error){
grunt.fatal(error);
}
};
if (grunt.option('debug')) {
exports.local.url += '?debug=' + grunt.option('debug');
}
exports.saucelabs = {
webdriver: {
remote: {
/* https://github.com/admc/wd/blob/master/README.md#named-parameters */
user: process.env.SAUCE_USERNAME || 'React',
pwd: process.env.SAUCE_ACCESS_KEY || '339d32ca-d594-4570-a3c2-94c50a91919b',
protocol: 'http:',
hostname: 'ondemand.saucelabs.com',
port: '80',
path: '/wd/hub'
}
},
desiredCapabilities: {
"build": process.env.TRAVIS_BUILD_NUMBER || 'dev' + Date.now(),
"tunnel-identifier": process.env.TRAVIS_JOB_NUMBER || 'my awesome tunnel',
"browserName": "chrome"
},
url: exports.local.url,
onStart: function(browser){
grunt.log.writeln("Starting WebDriver Test. Watch results here: http://saucelabs.com/tests/" + browser.sessionID);
},
onComplete: exports.local.onComplete,
onError: exports.local.onError
};
/* https://saucelabs.com/docs/platforms */
exports.saucelabs_ios =
exports.saucelabs_ios6_1 = sauceItUp({ browserName: 'iphone', version: '6.1', platform:'OS X 10.8' });
exports.saucelabs_ios6 = sauceItUp({ browserName: 'iphone', version: '6', platform:'OS X 10.8' });
exports.saucelabs_ios5_1 = sauceItUp({ browserName: 'iphone', version: '5.1', platform:'OS X 10.8' });
exports.saucelabs_ios5 = sauceItUp({ browserName: 'iphone', version: '5', platform:'OS X 10.6' });
exports.saucelabs_ios4 = sauceItUp({ browserName: 'iphone', version: '4', platform:'OS X 10.6' });
exports.saucelabs_ipad =
exports.saucelabs_ipad6_1 = sauceItUp({ browserName: 'ipad', version: '6.1', platform:'OS X 10.8' });
exports.saucelabs_ipad6 = sauceItUp({ browserName: 'ipad', version: '6', platform:'OS X 10.8' });
exports.saucelabs_ipad5_1 = sauceItUp({ browserName: 'ipad', version: '5.1', platform:'OS X 10.8' });
exports.saucelabs_ipad5 = sauceItUp({ browserName: 'ipad', version: '5', platform:'OS X 10.6' });
exports.saucelabs_ipad4 = sauceItUp({ browserName: 'ipad', version: '4', platform:'OS X 10.6' });
exports.saucelabs_android = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux' });
exports.saucelabs_android_tablet = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux', 'device-type':'tablet' });
exports.saucelabs_safari = sauceItUp({ browserName: 'safari' });
exports.saucelabs_chrome = sauceItUp({ browserName: 'chrome' });
exports.saucelabs_firefox = sauceItUp({ browserName: 'firefox' });
exports.saucelabs_ie =
exports.saucelabs_ie8 = sauceItUp({ browserName: 'internet explorer', version: 8 });
exports.saucelabs_ie9 = sauceItUp({ browserName: 'internet explorer', version: 9 });
exports.saucelabs_ie10 = sauceItUp({ browserName: 'internet explorer', version: 10 });
exports.saucelabs_ie11 = sauceItUp({ browserName: 'internet explorer', version: 11, platform:'Windows 8.1' });
function sauceItUp(desiredCapabilities) {
desiredCapabilities["build"] = exports.saucelabs.desiredCapabilities["build"];
desiredCapabilities["tunnel-identifier"] = exports.saucelabs.desiredCapabilities["tunnel-identifier"];
return {
webdriver: exports.saucelabs.webdriver,
url: exports.saucelabs.url,
onStart: exports.saucelabs.onStart,
onComplete: exports.saucelabs.onComplete,
onError: exports.saucelabs.onError,
desiredCapabilities: desiredCapabilities,
};
}
});

View File

@ -0,0 +1,48 @@
'use strict';
var grunt = require('grunt');
var tests = grunt.file.expand(__dirname + '/../../perf/tests/*');
var maxTime = 5;
var reactVersions = [
'edge',
'previous'
];
var params = []
.concat('headless=false')
.concat('maxTime=' + maxTime)
.concat(tests
.map(function(path){ return path.split(/tests./i).reverse()[0]; })
.map(encodeURIComponent)
.map(function(filename){ return 'test=' + filename; })
)
.concat(reactVersions
.map(encodeURIComponent)
.map(function(version){ return 'react=' + version }
)
);
module.exports = require('./webdriver-all')({
url: "http://127.0.0.1:9999/perf/index.html?" + params.join('&'),
isDoneTimeout: 15 * 60 * 1000,
onStart: function(){
grunt.event.on('perf results', function(results){
console.log(results);
});
},
onComplete: function(completedTestKeys){
grunt.verbose.writeln('onComplete ' + JSON.stringify(completedTestKeys));
},
onError: function(error){
grunt.fatal(error);
}
});

View File

@ -0,0 +1,47 @@
"use strict";
var grunt = require('grunt');
var http = require('http');
var fs = require('fs');
module.exports = function() {
var completedSuccessfully = this.async();
get(
"http://react.zpao.com/builds/master/latest/react.min.js",
__dirname + '/../../build/react-previous.min.js',
function(success){
if (!success) {
return completedSuccessfully(success);
}
get(
"http://react.zpao.com/builds/master/latest/JSXTransformer.js",
__dirname + '/../../build/JSXTransformer-previous.js',
completedSuccessfully
);
}
);
function get(url, targetFilePath, completedSuccessfully) {
grunt.verbose.writeln('getting url "' + url + '"');
http.get(url, function(response) {
grunt.verbose.writeln('Received status code ' + response.statusCode + ' for "' + url + '"');
if (response.statusCode != 200) {
if (response.headers.location) {
get(response.headers.location, targetFilePath);
return;
} else {
grunt.fatal('Nothing else to do.');
completedSuccessfully(false);
return;
}
}
grunt.verbose.writeln('Writing url to "' + targetFilePath + '"');
response.pipe(fs.createWriteStream(targetFilePath))
.on('close', function() {
completedSuccessfully(true);
})
;
});
}
};

View File

@ -0,0 +1,73 @@
/* jshint evil: true */
'use strict';
var grunt = require("grunt");
var wd = require('wd');
module.exports = function task(getJSReport){
var config = this.data;
var taskSucceeded = this.async();
getJSReport = getJSReport.bind(this, config, wd);
var desiredCapabilities = {};
if (config.desiredCapabilities) {
Object.keys(config.desiredCapabilities).forEach(function(key) {
if (config.desiredCapabilities[key] === undefined) {
return;
}
desiredCapabilities[key] = config.desiredCapabilities[key];
});
}
grunt.verbose.writeln("desiredCapabilities", JSON.stringify(desiredCapabilities));
var browser = wd.promiseChainRemote(config.webdriver.remote);
browser.on('status', function(info) {
grunt.verbose.writeln(info);
});
browser.on('command', function(meth, path, data) {
grunt.verbose.writeln(' > ' + meth, path, data || '');
});
var results = null;
// browser._debugPromise();
browser
.init(desiredCapabilities)
.then(config.onStart && config.onStart.bind(config, browser))
.get(config.url)
.then(function(){return browser;})
.then(getJSReport)
.then(function(data){ results = data; })
.fail(function(error){
grunt.log.error(error);
return browser
.eval('document.documentElement.innerText || document.documentElement.textContent')
.then(grunt.verbose.writeln.bind(grunt.verbose))
.then(function(){ throw error; })
;
})
.finally(function(){
if (grunt.option('webdriver-keep-open')) {
return;
}
grunt.verbose.writeln('Closing the browser window. To keep it open, pass the --webdriver-keep-open flag to grunt.');
return browser.quit();
})
.done(
function() {
if (config.onComplete) {
config.onComplete(results);
}
taskSucceeded(true);
},
function(error) {
if (config.onError) {
config.onError(error);
}
taskSucceeded(false);
}
);
};

View File

@ -2,82 +2,16 @@
'use strict';
var grunt = require("grunt");
var wd = require('wd');
module.exports = function(){
var config = this.data;
var taskSucceeded = this.async();
var desiredCapabilities = {};
if (config.desiredCapabilities) {
Object.keys(config.desiredCapabilities).forEach(function(key) {
if (config.desiredCapabilities[key] === undefined) {
return;
}
desiredCapabilities[key] = config.desiredCapabilities[key];
});
}
grunt.verbose.writeln("desiredCapabilities", JSON.stringify(desiredCapabilities));
var browser = wd.promiseChainRemote(config.webdriver.remote);
browser.on('status', function(info) {
grunt.verbose.writeln(info);
return require('./webdriver-all').call(this, function(config, wd, browser){
return browser
.waitFor(wd.asserters.jsCondition("typeof window.jasmine != 'undefined'"), 5e3, 50)
.fail(function(error){
throw Error("The test page didn't load properly. " + error);
})
.waitFor(wd.asserters.jsCondition("typeof window.jasmine.getJSReport != 'undefined'"), 60e3, 100)
.waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
.eval("jasmine.getJSReport().passed")
;
});
browser.on('command', function(meth, path, data) {
grunt.verbose.writeln(' > ' + meth, path, data || '');
});
var results = null;
// browser._debugPromise();
browser
.init(desiredCapabilities)
.then(config.onStart && config.onStart.bind(config, browser))
.get(config.url)
.then(function(){return browser;})
.then(getJSReport)
.then(function(data){ results = data; })
.fail(function(error){
grunt.log.error(error);
return browser
.eval('document.documentElement.innerText || document.documentElement.textContent')
.then(grunt.verbose.writeln.bind(grunt.verbose))
.then(function(){ throw error; })
;
})
.finally(function(){
if (grunt.option('webdriver-keep-open')) {
return;
}
grunt.verbose.writeln('Closing the browser window. To keep it open, pass the --webdriver-keep-open flag to grunt.');
return browser.quit();
})
.done(
function() {
if (config.onComplete) {
config.onComplete(results);
}
taskSucceeded(true);
},
function(error) {
if (config.onError) {
config.onError(error);
}
taskSucceeded(false);
}
);
};
function getJSReport(browser){
return browser
.waitFor(wd.asserters.jsCondition("typeof window.jasmine != 'undefined'"), 5e3, 50)
.fail(function(error){
throw Error("The test page didn't load properly. " + error);
})
.waitFor(wd.asserters.jsCondition("typeof window.jasmine.getJSReport != 'undefined'"), 60e3, 100)
.waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
.eval("jasmine.getJSReport().passed");
}

View File

@ -0,0 +1,23 @@
/* jshint evil: true */
'use strict';
var grunt = require('grunt');
module.exports = function(){
return require('./webdriver-all').call(this, function(config, wd, browser){
if (!config.isDoneTimeout) {
grunt.verbose.writeln('Expected isDoneTimeout config, using default value');
}
grunt.verbose.writeln('isDoneTimeout:' + config.isDoneTimeout);
return browser
.waitFor(wd.asserters.jsCondition("window.isDone === false"), 5e3, 50)
.fail(function(error){
throw Error("The test page didn't load properly. " + error);
})
.waitFor(wd.asserters.jsCondition("window.isDone === true"), config.isDoneTimeout || 30e3, 1e3)
.waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
.eval("window.completedTestKeys || window._unhandledError")
;
});
};

View File

@ -38,6 +38,7 @@
"jstransform": "~2.0.1"
},
"devDependencies": {
"benchmark": "~1.0.0",
"browserify": "~2.36.1",
"coverify": "~0.1.1",
"envify": "~1.0.1",
@ -53,8 +54,10 @@
"grunt-contrib-jshint": "~0.7.2",
"gzip-js": "~0.3.2",
"jasmine-tapreporter": "~0.2.2",
"microtime": "~0.5.1",
"optimist": "~0.6.0",
"phantomjs": "~1.9",
"platform": "~1.0.0",
"populist": "~0.1.6",
"recast": "~0.5.6",
"sauce-tunnel": "~1.1.0",

66
perf/index.html Normal file
View File

@ -0,0 +1,66 @@
<!doctype html>
<meta charset=utf-8>
<title>Perf Tests</title>
<script>
window.onerror = function(error){
window._unhandledError = error;
window.isDone = true;
}
</script>
<script src="./lib/perf-test-runner.browser.js"></script>
<script> perfRunner.Polyfill(); </script>
<script src="../build/react.js"></script>
<script src="../build/JSXTransformer.js"></script>
<script src="../test/lib/postDataToURL.browser.js"></script>
<script src="./lib/BrowserPerfRunnerContext.react.js"></script>
<script src="./lib/BrowserPerfRunnerApp.react.js"></script>
<script>
var tests = [
"sanity.js",
"todolist-mount.js",
"todolist-edit.js",
"todolist-add.js",
"todolist-do-stuff.js",
"setState-callback-5.js",
"setState-callback.js",
"basic-div.js",
"basic-unmount.js",
"renderComponent-basic.js",
"shouldComponentUpdate.js",
];
var reactVersions = [
'edge',
'builds/master/latest'
];
window.onload = function(){
window.isDone = false;
React.renderComponent(
BrowserPerfRunnerApp({
headless: perfRunner.getQueryParamArrayOrDefault('headless', [false])[0],
react: perfRunner.getQueryParamArrayOrDefault('react', reactVersions),
tests: perfRunner.getQueryParamArrayOrDefault('test', tests),
maxTime: perfRunner.getQueryParamArrayOrDefault('maxTime', [5])[0],
onCompleteEach: function(results){
console.log('onCompleteEach', results);
postDataToURL({type:'perf', message:results}, '/reportTestResults');
},
onError: function(error){
window._unhandledError = error;
},
onComplete: function(results){
window.completedTestKeys = Object.keys(results);
window.isDone = true;
}
}),
document.body
);
}
</script>

View File

@ -0,0 +1,205 @@
var BrowserPerfRunnerApp = React.createClass({
propTypes: {
tests: React.PropTypes.array.isRequired,
react: React.PropTypes.array.isRequired,
maxTime: React.PropTypes.number,
onCompleteEach: React.PropTypes.func,
onComplete: React.PropTypes.func,
onError: React.PropTypes.func,
headless: React.PropTypes.bool
},
getInitialState: function(){
var queue = [];
this.props.tests.forEach(function(testName){
this.props.react.forEach(function(version){
queue.push({
test: testName,
react: version
});
},this);
},this);
return {
queue: queue,
results: {}
};
},
handleResults: function(results){
this.state.results[results.test + '@' + results.react] = results;
this.replaceState(this.state);
},
handleComplete: function(queueItem){
queueItem.completed = true;
if (!this.props.onCompleteEach) {
return;
}
// Can't get the resultsForAllVersions if there are still some queued
var incompleteCount = 0;
for (var index = this.state.queue.length; --index >= 0;){
if (this.state.queue[index].completed) {
continue;
}
if (this.state.queue[index].test === queueItem.test) {
return;
}
incompleteCount ++;
}
var resultsForAllVersions = Object.keys(this.state.results)
.filter(function(key){return key.indexOf(queueItem.test) === 0;})
.map(function(key){return this.state.results[key];}, this)
;
this.props.onCompleteEach(resultsForAllVersions);
if (this.props.onComplete && incompleteCount === 0) {
this.props.onComplete(this.state.results);
}
},
render: function(){
var grid = null;
if (!this.props.headless) {
grid = GridViewTable({
rows: this.props.tests,
cols: this.props.react,
renderCell: BrowserPerfRunnerApp.renderBenchmarkCell,
value: this.state.results
});
}
return React.DOM.div(null,
BenchmarkQueue({
initialQueue: this.state.queue,
onChange: this.handleResults,
maxTime: this.props.maxTime,
onCompleteEach: this.handleComplete,
onError: this.props.onError
}),
grid
);
}
});
BrowserPerfRunnerApp.renderBenchmarkCell = function(props, row, col){
if (col == null && row == null) return React.DOM.th(null);
if (row == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'center'}}, col);
var benchmarks = Object.keys(props.value)
.filter(function(key){
return key.indexOf(row) === 0;
})
.map(function(key){
return props.value[key];
})
.filter(function(benchmark){
return benchmark && !benchmark.isRunning && benchmark.stats;
})
;
if (col == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'right'}},
React.DOM.a({href:'?test=' + row}, benchmarks[0] && benchmarks[0].name || row)
);
var key = row + '@' + col;
var benchmark = props.value[key];
if (!(benchmark && benchmark.stats)) return React.DOM.td({key:key});
var colors = [
'000000',
'AA0000',
'00AA00',
'AA5500',
'0000AA',
'AA00AA',
'00AAAA',
'AAAAAA',
'555555',
'FF5555',
'55FF55',
'FFFF55',
'5555FF',
'FF55FF',
'55FFFF',
'FFFFFF'
];
function chartValue(value){
return Math.round(valueFromRangeToRange(value, chartValue.min, chartValue.max, 0, 100));
}
chartValue.min = Math.min.apply(Math, benchmarks.map(function(benchmark){return Math.min.apply(Math, benchmark.stats.sample);}));
chartValue.max = Math.max.apply(Math, benchmarks.map(function(benchmark){return Math.max.apply(Math, benchmark.stats.sample);}));
var means = benchmarks.map(function(benchmark){
return benchmark.stats.mean;
});
benchmarks.forEach(function(benchmark){
benchmark.isTheWinner = benchmark.stats.mean <= Math.min.apply(Math, means);
});
var chartValues = benchmarks.map(function(benchmark){
// benchmark.stats.sample.sort(function(a,b){return b - a;});
return benchmark.stats.sample.map(chartValue).join(',');
}).join('|');
return (
React.DOM.td({key:key, style:{textAlign:'center', width:234, verticalAlign:'top'}},
benchmark.error && benchmark.error.message || '',
React.DOM.div({style: benchmark.isTheWinner ? { backgroundColor:'#0A5', color:'#AFA' } : {backgroundColor:'transparent', color:'inherit'}},
Math.round(1 / benchmark.stats.mean * 100) / 100, " op/s ",
React.DOM.strong(null, Math.round(benchmark.stats.mean * 1000 * 100) / 100, " ms/op "),
React.DOM.small(null, "(±" + (Math.round(benchmark.stats.rme * 10) / 10) + "%)")
),
benchmark.isRunning && 'Running' || React.DOM.img({
style: {
borderWidth: 2,
borderStyle: 'solid',
color: '#' + colors[benchmarks.indexOf(benchmark)]
},
width: 230,
height: 50,
src: 'https://chart.googleapis.com/chart?cht=ls&chs=460x100&chd=t:' + chartValues + '&chco=' + colors.join(',')
})
)
);
}
function valueFromRangeToRange(value, fromMin, fromMax, toMin, toMax){
var fromRange = fromMax - fromMin;
var toRange = toMax - toMin;
return (((value - fromMin) * toRange) / fromRange) + toMin;
}
var GridViewTable = React.createClass({
propTypes: {
rows: React.PropTypes.array.isRequired,
cols: React.PropTypes.array.isRequired,
renderCell: React.PropTypes.func.isRequired
},
_renderCell: function(col){
return this.props.renderCell({ value:this.props.value }, this._row, col);
},
_renderRow: function(row){
this._row = row;
return React.DOM.tr({key:row},
this._renderCell(null, 0),
this.props.cols.map(this._renderCell, this)
);
},
render: function(){
return React.DOM.table(null,
this._renderRow(null, 0),
this.props.rows.map(this._renderRow, this)
);
}
});

View File

@ -0,0 +1,195 @@
var BenchmarkQueue = React.createClass({
propTypes: {
debug: React.PropTypes.bool,
onChange: React.PropTypes.func.isRequired,
initialQueue: React.PropTypes.array.isRequired,
maxTime: React.PropTypes.number,
onCompleteEach: React.PropTypes.func,
onError: React.PropTypes.func
},
getDefaultProps: function(){
return {
maxTime: 5
};
},
getInitialState: function(){
return {
queue: this.props.initialQueue.slice()
};
},
setItemState: function(state){
state.test = this.state.queue[0].test;
state.react = this.state.queue[0].react;
this.props.onChange(state);
},
handleContextReady: function(window){
var benchmark = window.Benchmark(window.exports);
benchmark.options.maxTime = this.props.maxTime; //DEBUG
var itemState = {
testRunnerURL: window.location.href,
name: window.exports.name,
platform: window.Benchmark.platform.description,
reactVersion: window.React.version,
isMinified: (function(){
var code = window.React.renderComponent.toString();
return code.indexOf(',') - code.indexOf('(') <= 2;
}())
};
this.setItemState(itemState);
var self = this;
benchmark.on('start error cycle complete', function(){
var stats = JSON.parse(JSON.stringify(benchmark.stats));
itemState.stats = stats;
itemState.isRunning = benchmark.running;
itemState.error = benchmark.error;
self.setItemState(itemState);
});
if (this.props.onError) {
benchmark.on('error', this.props.onError);
}
benchmark.on('complete', function(){
var queue = self.state.queue.slice();
var queueItem = queue.shift();
if (self.props.onCompleteEach) {
self.props.onCompleteEach(queueItem);
}
self.setState({ queue:queue });
});
benchmark.run({async:true});
},
shouldComponentUpdate: function(nextProps, nextState){
return nextState.queue.length < this.state.queue.length;
},
render: function(){
if (!(this.state.queue && this.state.queue.length > 0)){
return React.DOM.div({style:{display:'none'}});
}
return BrowserPerfRunnerContext({
debug: this.props.debug,
test: this.state.queue[0].test,
react: this.state.queue[0].react,
onReady: this.handleContextReady
});
}
});
var BrowserPerfRunnerContext = React.createClass({
propTypes: {
debug: React.PropTypes.bool,
test: function(object, key){
React.PropTypes.string.isRequired(object, key);
if (/\.jsx?$/i.test(object[key])) return;
throw Error('Expected `' + key + '` to be a test file name with extension `.js` or `.jsx`');
},
react: function(object, key){
React.PropTypes.string.isRequired(object, key);
if (/^(?:builds\/.+|edge|previous|(?:\d+\.){2}\d+)$/.test(object[key])) return;
throw Error('Expected `' + key + '` prop to be a valid react version string, build string or "edge" or "previous"');
},
onReady: React.PropTypes.func.isRequired
},
getInitialState: function(){
return {
testRunnerURL:'about:blank'
};
},
// _handleFrameError: function(error){
// console.error('BrowserPerfRunnerContext', error);
// },
//
// _handleFrameLoad: function(event){
// console.log('BrowserPerfRunnerContext', event);
// },
//
_handleMessage: function(event){
if (location.href.indexOf(event.origin) !== 0)
return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message from ' + event.origin);
if (event.source.location.href.indexOf(this.state.testRunnerURL) === -1)
return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message from ' + event.source.location.href);
if (event.data !== 'Ready!')
return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message ' + JSON.stringify(event.data));
this.props.onReady(event.source);
},
_getTestRunnerURL: function(props){
return 'runner.html' +
'?' +
'debug=' + (props.debug ? 1 : 0) +
'&' +
'react=' + encodeURIComponent(props.react) +
'&' +
'test=' + encodeURIComponent(props.test)
},
_renderState: function(props){
return {
testRunnerURL: this._getTestRunnerURL(props)
};
},
componentDidMount: function(){
var node = this.refs.iframe.getDOMNode();
// node.onload = this._handleFrameLoad;
// node.onerror = this._handleFrameError;
if (window.addEventListener) {
window.addEventListener('message', this._handleMessage, false);
} else if (window.attachEvent) {
window.attachEvent('onmessage', this._handleMessage);
} else {
throw Error('cannot attach onmessage listener');
}
this.setState(this._renderState(this.props));
},
componentWillUnmount: function(){
if (window.removeEventListener) {
window.removeEventListener('message', this._handleMessage);
} else if (window.detachEvent) {
window.detachEvent('onmessage', this._handleMessage);
} else {
throw Error('cannot detach onmessage listener');
}
this.refs.iframe.getDOMNode().src = '';
},
componentWillReceiveProps: function(nextProps){
this.setState(this._renderState(nextProps));
},
shouldComponentUpdate: function(nextProps, nextState){
return nextState.testRunnerURL != this.state.testRunnerURL;
},
render: function(){
return (
React.DOM.iframe({
ref: 'iframe',
name: "BrowserPerfRunnerContextFrame",
style: this.style,
src: this.state.testRunnerURL
})
);
},
style: {
position: 'absolute',
right: '100%',
bottom: '100%'
}
});

View File

@ -0,0 +1,204 @@
if (typeof console == 'undefined') console = {
log: function(){},
warn: function(){},
error: function(){},
debug: function(){}
};
var perfRunner;
if (typeof exports == 'object') {
perfRunner = exports;
} else {
perfRunner = {};
}
perfRunner.assert = function(test, message){
if (typeof test == 'function') test = test();
if (test) return;
throw Error(message);
}
perfRunner.WriteScript = function(props){
var type = '';
if (props.jsx) {
type = ' type="text/jsx"';
}
var src = props.src;
if (!props.cache) {
src += src.indexOf('?') === -1 ? '?_' : '&_';
src += perfRunner.WriteScript.cacheBust;
}
document.write('<script' + type + ' src="' + src + '"><\/script>');
}
perfRunner.WriteScript.cacheBust = (+new Date).toString(36);
perfRunner.WriteReactLibScript = function(params){
var src;
var minSuffix;
if (params.debug) {
minSuffix = '';
} else {
minSuffix = '.min';
}
if (params.version && typeof params.version != 'string') throw TypeError("Expected 'version' to be a string");
if (params.version == 'edge' || !params.version) {
console.log('React edge (local)');
perfRunner.WriteScript({src:'../build/react' + minSuffix + '.js'});
perfRunner.WriteScript({src:'../build/JSXTransformer.js'});
} else if (params.version == 'previous') {
console.log('React previous (local)');
perfRunner.WriteScript({cache:true, src:'../build/react-previous' + minSuffix + '.js'});
perfRunner.WriteScript({cache:true, src:'../build/JSXTransformer-previous.js'});
} else if (params.version.indexOf('builds/') === 0) {
perfRunner.WriteScript({cache:true, src:'http://react.zpao.com/' + params.version + '/react' + minSuffix + '.js'});
perfRunner.WriteScript({cache:true, src:'http://react.zpao.com/' + params.version + '/JSXTransformer.js'});
} else {
console.log('React ' + params.version);
perfRunner.WriteScript({cache:true, src:'http://fb.me/react-' + params.version + minSuffix + '.js'});
perfRunner.WriteScript({cache:true, src:'http://fb.me/JSXTransformer-' + params.version + '.js'});
}
if (params.debug) {
console.warn('Loading the unminified build of React, performance may suffer.');
console.warn('Load "' + location.href.replace(/\bdebug=\w+&?|&\bdebug=\w+/ig, '') + '" for better perf.');
} else {
console.warn('Loading the minified build of React, debugging may be harder.');
console.warn('Load "' + location.href.replace(/\bdebug=\w+&?|&\bdebug=\w+/ig, '') + '&debug=1' + '" for easier debugging.');
}
}
perfRunner.WriteTestScript = function(params){
if (Array.isArray(params.test)) {
return params.test
.map(function(test){return {test:test};})
.map(perfRunner.WriteTestScript)
;
}
perfRunner.assert(params.test.indexOf('..') === -1, 'no relative paths allowed');
return perfRunner.WriteScript({jsx:true, src: './tests/' + params.test});
}
perfRunner.getQueryParamArray = function(key){
var values;
var queryString = location.search.substr(1);
var _key = encodeURIComponent(key) + '=';
if (queryString.indexOf(_key) > -1) {
values = queryString
.split(_key)
.slice(1)
.map(function(part){return part.split('&')[0];})
.map(decodeURIComponent)
.map(function(string){
try {
return JSON.parse(string);
} catch(e){}
return string;
})
;
}
perfRunner.assert(values && values.length && values[0], 'expected ' + key + ' query param');
return values;
}
perfRunner.getQueryParamArrayOrDefault = function(key, defaultValue){
try {
return perfRunner.getQueryParamArray(key);
} catch (e) {}
return defaultValue;
}
perfRunner.Polyfill = function(){
if (typeof Function.prototype.bind != 'undefined') return;
perfRunner.WriteScript({src:'/node_modules/es5-shim/es5-shim.js', cache:true});
perfRunner.WriteScript({src:'/node_modules/es5-shim/es5-sham.js', cache:true});
}
perfRunner.BenchmarkResults = function(props){
return perfRunner.roundNumberWithPrecision(props.stats.mean * 1000) + 'ms/op'
}
perfRunner.roundNumberWithPrecision = function(number, precision){
if (!precision) precision = 1000;
return Math.round(number * precision) / precision;
}
perfRunner.quickBench = function(benchmarkOptions, onComplete, onBeforeStart){
var bench = new Benchmark(benchmarkOptions);
if (onBeforeStart) onBeforeStart(null, bench);
bench.on('error', function(event){
console.error(event.message);
console.log(event.target.compiled.toString());
onComplete(Error(event.error));
});
bench.on('start', function(){
console.log('starting', bench.name);
});
bench.on('cycle', function(){
var bench = this,
size = bench.stats.size;
if (!bench.aborted) {
console.warn(bench.name + ' × ' + bench.count +
' (' + bench.stats.sample.length + ' samples)' +
' (' + Math.round(1 / bench.stats.mean) + ' ops/sec' + ')' +
' (' + (bench.stats.mean * 1000) + ' ms/op' + ')' +
' (±' + bench.stats.rme.toFixed(2) + '%)' +
' with ' + React.version
);
}
});
bench.on('complete', function(){
var results = {
platform: Benchmark.platform.description,
react: React.version,
name: bench.name,
// times: bench.times,
// stats: bench.stats
};
results['s/op'] = bench.stats.mean
results['ms/op'] = results['s/op'] * 1000
results['op/s'] = 1 / results['s/op']
results["% frame 60"] = results['ms/op'] / (1000 / 60) * 100
console.log(results);
onComplete(null, results);
});
bench.run();
};
perfRunner.singleTest = function(benchmarkOptions, onComplete){
var bench = Benchmark(exports);
bench.on('complete', function(){
var results = {
platform: Benchmark.platform.description,
react: React.version,
name: bench.name,
stats: bench.stats
};
onComplete(results);
});
bench.run();
}
perfRunner.ViewObject = function(props){
var value = props.value;
delete props.value;
if (typeof value != 'object') return React.DOM.span(props, [JSON.stringify(value), " ", typeof value]);
return React.DOM.table(props, Object.keys(value).map(function(key){
return React.DOM.tr(null,
React.DOM.th(null, key),
React.DOM.td(null, perfRunner.ViewObject({key:key, value:value[key]}))
);
}));
}

View File

@ -0,0 +1,117 @@
/*global*/todolist = {};
todolist.ID = Date.now();
todolist.now = window.performance && window.performance.now && window.performance.now.bind(window.performance) || Date.now.bind(Date);
todolist.App = React.createClass({
propTypes: {
fakeDataCount: React.PropTypes.number
},
getInitialState: function(){
var todos;
if (this.props.fakeDataCount) {
todos = Array(this.props.fakeDataCount + 1).join(',').split(',').map(function(ignore, index){
return {id:index, title:"Title " + index + " " + Math.random().toString(36).substring(2,16), completed:!!(index % 2)};
});
}
return {
timerStart: todolist.now(),
timerEnd: null,
timerEvent: 'getInitialState',
todos: todos || this.props.initialData || []
};
},
componentWillUpdate: function(props, state){
state.todos = state.todos.filter(function(todo){
return !todo.deleted;
});
},
addItem: function(title, callback){
if (title == null || title === '') {
var error = Error('invalid title');
if (!callback) throw error;
return callback(error);
}
var todos = this.state.todos.slice();
var todo = {
id: ++todolist.ID,
title: title,
completed: false
};
todos.push(todo);
if (callback) callback = callback.bind(this, todo);
this.setState({ timerEvent:'addItem', timerStart:todolist.now(), timerEnd:null, todos:todos }, callback);
return todo;
},
deleteItemById: function(id, callback){
var todo = this._getById(id);
if (!todo) return callback && callback(Error('todo with id ' + id + ' not found'));
todo.deleted = true;
this.setState({ timerEvent:'deleteItemById', timerStart:todolist.now(), timerEnd:null, todos:this.state.todos }, callback);
},
setItemCompleted: function(id, completed, callback){
var todo = this._getById(id);
if (!todo) return callback && callback(Error('todo with id ' + id + ' not found'));
todo.completed = completed;
this.setState({ timerEvent:'setItemCompleted', timerStart:todolist.now(), timerEnd:null, todos:this.state.todos }, callback);
},
_getById: function(id){
id = +id;
var todos = this.state.todos;
for (var index = todos.length; --index >= 0;){
if (todos[index].id === id) return todos[index];
}
return null;
},
_handleItemCompletedCheckboxChange: function(event){
var node = event.target;
this.setItemCompleted(node.value, node.checked);
},
_handleItemDeletedButton: function(event){
var node = event.target;
this.deleteItemById(node.value);
},
_renderTodoItem: function(todo, index){
return (
React.DOM.li({key:todo.id},
React.DOM.button({value:todo.id, onClick:this._handleItemDeletedButton}, 'x'),
React.DOM.input({type:"checkbox", value:todo.id, checked:todo.completed, onChange:this._handleItemCompletedCheckboxChange}),
" ",
React.DOM.span({style:{"text-decoration":todo.completed ? "line-through" : ""}}, todo.title)
)
);
},
render: function(){
if (!this.state.timerEnd) this.state.timerEnd = todolist.now();
if (this.props.onRender) this.props.onRender();
return (
React.DOM.div(null,
React.DOM.h1(null, "TODO"),
React.DOM.h3(null, this.state.timerEvent, " ", this.state.timerEnd - this.state.timerStart, 'ms'),
todolist.NewItemForm({onEnter:this.addItem, autoFocus:true}),
React.DOM.ol(null, this.state.todos.map(this._renderTodoItem))
)
);
},
componentDidMount: function(rootNode){
if (this.props.onDidMount) this.props.onDidMount(rootNode);
}
});
todolist.NewItemForm = React.createClass({
_handleNewItemKeyDown: function(event){
if (event.which !== 13/*enter key*/) return;
var node = this.refs.text.getDOMNode();
var value = node.value;
node.value = '';
this.props.onEnter(value);
return false;
},
render: function(){
return this.transferPropsTo(
React.DOM.input({ref:"text", onKeyDown:this.props.onEnter && this._handleNewItemKeyDown})
);
}
});

16
perf/lib/todolist.html Normal file
View File

@ -0,0 +1,16 @@
<!doctype html>
<meta charset=utf-8>
<title>todolist</title>
<script src="perf-test-runner.browser.js"></script>
<script> perfRunner.Polyfill(); </script>
<script src="../../build/react.min.js"></script>
<script src="todolist.browser.js"></script>
<script>
function main(){
var app = todolist.App({ fakeDataCount:333 });
React.renderComponent(app, document.body);
}
window.onload = main;
</script>

59
perf/runner.html Normal file
View File

@ -0,0 +1,59 @@
<!doctype html>
<meta charset=utf-8>
<title>Perf Test Runner</title>
<script src="../node_modules/platform/platform.js"></script>
<script src="../node_modules/benchmark/benchmark.js"></script>
<script src="lib/perf-test-runner.browser.js"></script>
<script> perfRunner.Polyfill(); </script>
<script>
perfRunner.WriteReactLibScript({
version: perfRunner.getQueryParamArrayOrDefault('react', [null])[0],
debug: perfRunner.getQueryParamArrayOrDefault('debug', [false])[0]
});
perfRunner.WriteScript({src:"lib/todolist.browser.js"});
perfRunner.WriteTestScript({ test:perfRunner.getQueryParamArray('test') });
</script>
<script>
function checkIfReady(){
if (!window.exports) return setTimeout(checkIfReady, 50);
console.timeStamp && console.timeStamp(perfRunner.getQueryParamArray('test'));
if (typeof gc == 'function') gc();
window.parent.postMessage(perfRunner.getQueryParamArrayOrDefault('callback', ['Ready!'])[0], '*');
}
checkIfReady();
function main(callback){
perfRunner.singleTest(exports, function(results){
document.getElementById('results').textContent = JSON.stringify(results, null, 2);
callback();
});
}
function runOnce(callback){
setTimeout(function(){
exports.fn({resolve: function(){
setTimeout(callback, 0);
}});
},0);
}
</script>
<button autofocus onclick="if (disabled) return false; main(function(){ disabled=false; textContent='Run again!'; }); textContent='Running!'; disabled=true">Run!</button>
<div>
<button onclick="exports.setup();">Setup!</button>
<button onclick="if (disabled) return false; runOnce(function(){ disabled=false; textContent='Run again!'; }); textContent='Running!'; disabled=true">Run once!</button>
<button onclick="exports.teardown();">Teardown!</button>
</div>
<pre id=results style="font-size:9px"></pre>

16
perf/tests/basic-div.js Normal file
View File

@ -0,0 +1,16 @@
/* jshint undef: true, unused: true */
/* global document */
/* global window */
/* global Benchmark */
/* global React */
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'React.DOM.div, no props';
exports.fn = function(){
React.DOM.div(null, 'lol, perf testing ', this.count);
};

View File

@ -0,0 +1,23 @@
/* jshint undef: true, unused: true */
/* global document */
/* global window */
/* global Benchmark */
/* global React */
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'unmountComponentAtNode';
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
var _firstChild = React.DOM.div(null, 'lol, perf testing ', this.count);
React.renderComponent(_firstChild, _rootNode);
};
exports.fn = function(){
if (React.unmountAndReleaseReactRootNode) React.unmountAndReleaseReactRootNode(_rootNode);
else React.unmountComponentAtNode(_rootNode);
};

View File

@ -0,0 +1,24 @@
/* jshint undef: true, unused: true */
/* global document */
/* global window */
/* global Benchmark */
/* global React */
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'React.renderComponent single div';
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
};
exports.fn = function(){
React.renderComponent(React.DOM.div(null, 'lol, perf testing ', this.count), _rootNode);
};
exports.teardown = function(){
if (React.unmountAndReleaseReactRootNode) React.unmountAndReleaseReactRootNode(_rootNode);
else React.unmountComponentAtNode(_rootNode);
};

15
perf/tests/sanity.js Normal file
View File

@ -0,0 +1,15 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'Trivial benchmark to verify that everything works';
exports.setup = function(){
var foo;
};
exports.fn = function(){
foo = Array(999).join('Howdy!\n');
};
exports.teardown = function(){
foo = null;
};

View File

@ -0,0 +1,41 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'From setState to callback (x5)';
exports.defer = true;
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
/*global*/setState = null;
var AwesomeComponent = React.createClass({
getInitialState: function(){
return { random:null };
},
render: function(){
if (!setState) setState = this.setState.bind(this);
return React.DOM.div(null, this.state.random);
}
});
React.renderComponent(AwesomeComponent(null), _rootNode);
};
exports.fn = function(deferred){
setState({random: Date.now() + Math.random()}, function(){
setState({random: Date.now() + Math.random()}, function(){
setState({random: Date.now() + Math.random()}, function(){
setState({random: Date.now() + Math.random()}, function(){
setState({random: Date.now() + Math.random()}, function(){
deferred.resolve();
});
});
});
});
});
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
};

View File

@ -0,0 +1,33 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'From setState to callback';
exports.defer = true;
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
/*global*/setState = null;
var AwesomeComponent = React.createClass({
getInitialState: function(){
return { random:null };
},
render: function(){
if (!setState) setState = this.setState.bind(this);
return React.DOM.div(null, this.state.random);
}
});
React.renderComponent(AwesomeComponent(null), _rootNode);
};
exports.fn = function(deferred){
setState({random: Date.now() + Math.random()}, function(){
deferred.resolve();
});
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
};

View File

@ -0,0 +1,25 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'shouldComponentUpdate';
exports.setup = function(){
var AwesomeComponent = React.createClass({
shouldComponentUpdate: function(){
return false;
},
render: function(){
return React.DOM.div({});
}
});
var _rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
};
exports.fn = function(){
React.renderComponent(AwesomeComponent(null), _rootNode);
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
};

View File

@ -0,0 +1,27 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'todolist from addItem to callback';
exports.defer = true;
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
/*global*/_app = todolist.App({ fakeDataCount: 333 });
React.renderComponent(_app, _rootNode);
};
exports.fn = function(deferred){
var liCount = document.getElementsByTagName('li').length;
_app.addItem(Math.random(), function(){
if (document.getElementsByTagName('li').length <= liCount) throw Error('expected a list item to be added to the dom');
deferred.resolve();
});
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
_rootNode.parentNode.removeChild(_rootNode);
_rootNode = null;
_app = null;
};

View File

@ -0,0 +1,54 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
/*global*/_timesToRun = 2;
exports.name = 'todolist add, complete, remove (x' + _timesToRun + ')';
exports.defer = true;
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
/*global*/_app = todolist.App({ fakeDataCount: 333 });
React.renderComponent(_app, _rootNode);
};
exports.fn = function(deferred){
var originalLiCount = document.getElementsByTagName('li').length;
var todos = [];
var times = _timesToRun;
while (times-- >= 0){
todos.push(_app.addItem(times+1));
}
todos.forEach(function(todo){
_app.setItemCompleted(todo.id);
});
todos.forEach(function(todo){
_app.deleteItemById(todo.id);
});
todos = null;
_app.addItem(Math.random(), function(todo){
if (document.getElementsByTagName('li').length <= originalLiCount)
throw Error('expected a list item to be added to the dom');
_app.deleteItemById(todo.id, function(){
if (document.getElementsByTagName('li').length != originalLiCount)
throw Error('expected everything to be done by now');
deferred.resolve();
});
});
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
_rootNode.parentNode.removeChild(_rootNode);
_rootNode = null;
_app = null;
};

View File

@ -0,0 +1,32 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'todolist setItemCompleted';
exports.defer = true;
exports.setup = function(){
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
/*global*/_app = todolist.App({ fakeDataCount: 333 });
React.renderComponent(_app, _rootNode);
/*global*/_todo1 = _app.addItem("Howdy 1!");
/*global*/_todo2 = _app.addItem("Howdy 2!");
/*global*/_todo3 = _app.addItem("Howdy 3!");
};
exports.fn = function(deferred){
_app.setItemCompleted(_todo1.id, !_todo1.completed);
_app.setItemCompleted(_todo2.id, !_todo2.completed);
_app.setItemCompleted(_todo3.id, !_todo3.completed, function(){
deferred.resolve();
});
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
_rootNode.parentNode.removeChild(_rootNode);
_rootNode = null;
_app = null;
};

View File

@ -0,0 +1,23 @@
if (typeof exports == 'undefined') exports = {};
/*http://benchmarkjs.com/docs#options*/
exports.name = 'todolist from renderComponent to renderComponent callback (333 rows)';
exports.defer = true;
exports.setup = function(){
if (typeof _rootNode != 'undefined') throw Error("should teardown before running setup again");
/*global*/_rootNode = document.createElement('div');
document.body.appendChild(_rootNode);
};
exports.fn = function(deferred){
React.renderComponent(todolist.App({ fakeDataCount: 333 }), _rootNode, function(){ deferred.resolve(); });
};
exports.teardown = function(){
React.unmountComponentAtNode(_rootNode);
_rootNode.parentNode.removeChild(_rootNode);
_rootNode = undefined;
};

View File

@ -11,6 +11,7 @@
'../node_modules/jasmine-tapreporter/src/tapreporter.js',
'../vendor/jasmine-jsreporter/jasmine-jsreporter.js',
'lib/postDataToURL.browser.js',
'lib/reportTestResults.browser.js',
'../build/react.js',

View File

@ -0,0 +1,34 @@
function createXMLHttpRequest(){
try{return new XMLHttpRequest();}
catch(e){}
try {return new ActiveXObject("Msxml2.XMLHTTP");}
catch (e) {}
try {return new ActiveXObject("Microsoft.XMLHTTP");}
catch (e) {}
}
function getURLSync(url){
var request = createXMLHttpRequest();
request.open('GET', url, /*asynchronous?*/false);
return request.responseText;
}
function postDataToURL(data, url, callback) {
if (!callback) callback = postDataToURL.defaultCallback;
var request = createXMLHttpRequest();
if (!request) return callback(Error('XMLHttpRequest is unsupported'));
postDataToURL.running = (postDataToURL.running||0) + 1;
request.onreadystatechange = function(){
if (request.readyState != 4) return;
request.onreadystatechange = null;
postDataToURL.running = (postDataToURL.running||0) - 1;
callback(request.status == 200 ? null : request.status, request.responseText);
};
request.open('POST', url);
request.setRequestHeader('Content-Type', 'application/json');
request.send(JSON.stringify(data));
}
postDataToURL.defaultCallback = function(error){
// console.log('postDataToURL.defaultCallback', arguments)
}

View File

@ -48,30 +48,3 @@ console._flush = function(){
};
}(window.jasmine.getEnv()));
function createXMLHttpRequest(){
try{return new XMLHttpRequest();}
catch(e){}
try {return new ActiveXObject("Msxml2.XMLHTTP");}
catch (e) {}
try {return new ActiveXObject("Microsoft.XMLHTTP");}
catch (e) {}
}
function postDataToURL(data, url, callback) {
if (!callback) callback = postDataToURL.defaultCallback;
var request = createXMLHttpRequest();
if (!request) return callback(Error('XMLHttpRequest is unsupported'));
postDataToURL.running = (postDataToURL.running||0) + 1;
request.onreadystatechange = function(){
if (request.readyState != 4) return;
request.onreadystatechange = null;
postDataToURL.running = (postDataToURL.running||0) - 1;
callback(request.status == 200 ? null : request.status, request.responseText);
};
request.open('POST', url);
request.setRequestHeader('Content-Type', 'application/json');
request.send(JSON.stringify(data));
}
postDataToURL.defaultCallback = function(error){
// console.log('postDataToURL.defaultCallback', arguments)
}