@samitouri / QOS-React-2 / commits / 91821007ed

benchmark runner

Thomas Aylott committed Dec 17, 2013 at 00:25 UTC 91821007ed640c0cfcba6b3c9d179c5bd11c4a50
32 files changed +1568 -182
.travis.yml
+2
@@ -26,6 +26,7 @@ env:
26 matrix:
27 - TEST_TYPE=test:full
28 - TEST_TYPE=lint
29 + - TEST_TYPE=perf:full
30 - TEST_TYPE=test:coverage
31 - TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11
32 - TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10
@@ -43,6 +44,7 @@ matrix:
44 allow_failures:
45 - env: TEST_TYPE=lint
46 - env: TEST_TYPE=test:coverage
47 + - env: TEST_TYPE=perf:full
48 - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie11
49 - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie10
50 - env: TEST_TYPE=test:webdriver:saucelabs BROWSER_NAME=ie9
Gruntfile.js
+35 -1
@@ -21,7 +21,8 @@ module.exports = function(grunt) {
21 browserify: require('./grunt/config/browserify'),
22 populist: require('./grunt/config/populist'),
23 connect: require('./grunt/config/server')(grunt),
24 - "webdriver-jasmine": require('./grunt/config/webdriver-jasmine.js'),
24 + "webdriver-jasmine": require('./grunt/config/webdriver-jasmine'),
25 + "webdriver-perf": require('./grunt/config/webdriver-perf'),
26 npm: require('./grunt/config/npm'),
27 clean: ['./build', './*.gem', './docs/_site', './examples/shared/*.js', '.module-cache'],
28 jshint: require('./grunt/config/jshint'),
@@ -39,6 +40,8 @@ module.exports = function(grunt) {
40 // Alias 'jshint' to 'lint' to better match the workflow we know
41 grunt.registerTask('lint', ['jshint']);
42
43 + grunt.registerTask('download-previous-version', require('./grunt/tasks/download-previous-version.js'));
44 +
45 // Register jsx:debug and :release tasks.
46 grunt.registerMultiTask('jsx', jsxTask);
47
@@ -51,6 +54,8 @@ module.exports = function(grunt) {
54
55 grunt.registerMultiTask('webdriver-jasmine', webdriverJasmineTasks);
56
57 + grunt.registerMultiTask('webdriver-perf', require('./grunt/tasks/webdriver-perf'));
58 +
59 grunt.registerMultiTask('npm', npmTask);
60
61 grunt.registerTask('npm-react:release', npmReactTasks.buildRelease);
@@ -67,6 +72,14 @@ module.exports = function(grunt) {
72 'version-check',
73 'browserify:withCodeCoverageLogging'
74 ]);
75 + grunt.registerTask('build:perf', [
76 + 'jsx:release',
77 + 'version-check',
78 + 'browserify:transformer',
79 + 'browserify:basic',
80 + 'browserify:min',
81 + 'download-previous-version'
82 + ]);
83 grunt.registerTask('build:test', [
84 'jsx:test',
85 'version-check',
@@ -84,6 +97,12 @@ module.exports = function(grunt) {
97 'webdriver-jasmine:local'
98 ]);
99
100 + grunt.registerTask('perf:webdriver:phantomjs', [
101 + 'connect',
102 + 'webdriver-phantomjs',
103 + 'webdriver-perf:local'
104 + ]);
105 +
106 grunt.registerTask('test:full', [
107 'build:test',
108 'build:basic',
@@ -98,6 +117,20 @@ module.exports = function(grunt) {
117 'webdriver-jasmine:saucelabs_chrome'
118 ]);
119
120 + grunt.registerTask('perf:full', [
121 + 'build:perf',
122 +
123 + 'connect',
124 + 'webdriver-phantomjs',
125 + 'webdriver-perf:local',
126 +
127 + 'sauce-tunnel',
128 + 'webdriver-perf:saucelabs_firefox',
129 + 'webdriver-perf:saucelabs_chrome',
130 + 'webdriver-perf:saucelabs_ie11',
131 + 'webdriver-perf:saucelabs_ie8',
132 + ]);
133 +
134 grunt.registerTask('test:webdriver:saucelabs', [
135 'build:test',
136 'build:basic',
@@ -137,6 +170,7 @@ module.exports = function(grunt) {
170 'coverage:parse'
171 ]);
172 grunt.registerTask('test', ['build:test', 'build:basic', 'test:webdriver:phantomjs']);
173 + grunt.registerTask('perf', ['build:perf', 'perf:webdriver:phantomjs']);
174 grunt.registerTask('npm:test', ['build', 'npm:pack']);
175
176 // Optimized build task that does all of our builds. The subtasks will be run
grunt/config/server.js
+2
@@ -44,6 +44,8 @@ module.exports = function(grunt){
44 coverageWriteStream.write(log.message + '\n');
45 } else if (log.type == 'coverage done') {
46 grunt.task.run('finalize-coverage-stream');
47 + } else if (log.type == 'perf') {
48 + grunt.event.emit('perf results', log.message);
49 } else {
50 grunt.verbose.writeln(log);
51 }
grunt/config/webdriver-all.js new
+113
@@ -0,0 +1,113 @@
1 +'use strict';
2 +
3 +var grunt = require('grunt');
4 +
5 +module.exports = function(props){
6 + if (typeof props.url != 'string') {
7 + throw TypeError('expected url string');
8 + }
9 + if ('isDoneTimeout' in props && typeof props.isDoneTimeout != 'number') {
10 + throw TypeError('expected isDoneTimeout to be a number');
11 + }
12 + if ('onStart' in props && typeof props.onStart != 'function') {
13 + throw TypeError('expected onStart to be a function');
14 + }
15 + if ('onComplete' in props && typeof props.onComplete != 'function') {
16 + throw TypeError('expected onComplete to be a function');
17 + }
18 + if ('onError' in props && typeof props.onError != 'function') {
19 + throw TypeError('expected onError to be a function');
20 + }
21 +
22 + var exports = {};
23 +
24 + exports.local = {
25 + webdriver: {
26 + remote: { protocol: 'http:', hostname: '127.0.0.1', port: 9515, path: '/' }
27 + },
28 + url: props.url,
29 + onStart: props.onStart,
30 + onComplete: props.onComplete,
31 + onError: props.onError,
32 + isDoneTimeout: props.isDoneTimeout
33 + };
34 +
35 + if (grunt.option('debug')) {
36 + exports.local.url += (exports.local.url.indexOf('?') == -1 ? '?' : '&') + 'debug=' + grunt.option('debug');
37 + }
38 +
39 + exports.saucelabs = {
40 + webdriver: {
41 + remote: {
42 + /* https://github.com/admc/wd/blob/master/README.md#named-parameters */
43 + user: process.env.SAUCE_USERNAME || 'React',
44 + pwd: process.env.SAUCE_ACCESS_KEY || '339d32ca-d594-4570-a3c2-94c50a91919b',
45 + protocol: 'http:',
46 + hostname: 'ondemand.saucelabs.com',
47 + port: '80',
48 + path: '/wd/hub'
49 + }
50 + },
51 + desiredCapabilities: {
52 + "build": process.env.TRAVIS_BUILD_NUMBER || 'dev' + Date.now(),
53 + "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER || 'my awesome tunnel',
54 + "browserName": "chrome"
55 + },
56 + url: exports.local.url,
57 + onStart: function(browser){
58 + grunt.log.writeln("Starting WebDriver Test. Watch results here: http://saucelabs.com/tests/" + browser.sessionID);
59 + if (props.onStart) {
60 + return props.onStart(browser);
61 + }
62 + },
63 + onComplete: exports.local.onComplete,
64 + onError: exports.local.onError,
65 + isDoneTimeout: exports.local.isDoneTimeout
66 + };
67 +
68 + /* https://saucelabs.com/platforms */
69 + exports.saucelabs_ios =
70 + exports.saucelabs_ios7 = sauceItUp({ browserName: 'iphone', version: '7', platform:'OS X 10.9' });
71 + exports.saucelabs_ios6_1 = sauceItUp({ browserName: 'iphone', version: '6.1', platform:'OS X 10.8' });
72 + exports.saucelabs_ios6 = sauceItUp({ browserName: 'iphone', version: '6', platform:'OS X 10.8' });
73 + exports.saucelabs_ios5_1 = sauceItUp({ browserName: 'iphone', version: '5.1', platform:'OS X 10.8' });
74 + exports.saucelabs_ios5 = sauceItUp({ browserName: 'iphone', version: '5', platform:'OS X 10.6' });
75 + exports.saucelabs_ios4 = sauceItUp({ browserName: 'iphone', version: '4', platform:'OS X 10.6' });
76 +
77 + exports.saucelabs_ipad =
78 + exports.saucelabs_ipad7 = sauceItUp({ browserName: 'ipad', version: '7', platform:'OS X 10.9' });
79 + exports.saucelabs_ipad6_1 = sauceItUp({ browserName: 'ipad', version: '6.1', platform:'OS X 10.8' });
80 + exports.saucelabs_ipad6 = sauceItUp({ browserName: 'ipad', version: '6', platform:'OS X 10.8' });
81 + exports.saucelabs_ipad5_1 = sauceItUp({ browserName: 'ipad', version: '5.1', platform:'OS X 10.8' });
82 + exports.saucelabs_ipad5 = sauceItUp({ browserName: 'ipad', version: '5', platform:'OS X 10.6' });
83 + exports.saucelabs_ipad4 = sauceItUp({ browserName: 'ipad', version: '4', platform:'OS X 10.6' });
84 +
85 + exports.saucelabs_android = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux' });
86 + exports.saucelabs_android_tablet = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux', 'device-type':'tablet' });
87 +
88 + exports.saucelabs_safari = sauceItUp({ browserName: 'safari' });
89 + exports.saucelabs_chrome = sauceItUp({ browserName: 'chrome' });
90 + exports.saucelabs_firefox = sauceItUp({ browserName: 'firefox' });
91 +
92 + exports.saucelabs_ie =
93 + exports.saucelabs_ie8 = sauceItUp({ browserName: 'internet explorer', version: 8 });
94 + exports.saucelabs_ie9 = sauceItUp({ browserName: 'internet explorer', version: 9 });
95 + exports.saucelabs_ie10 = sauceItUp({ browserName: 'internet explorer', version: 10 });
96 + exports.saucelabs_ie11 = sauceItUp({ browserName: 'internet explorer', version: 11, platform:'Windows 8.1' });
97 +
98 + function sauceItUp(desiredCapabilities) {
99 + desiredCapabilities["build"] = exports.saucelabs.desiredCapabilities["build"];
100 + desiredCapabilities["tunnel-identifier"] = exports.saucelabs.desiredCapabilities["tunnel-identifier"];
101 + return {
102 + webdriver: exports.saucelabs.webdriver,
103 + url: exports.saucelabs.url,
104 + onStart: exports.saucelabs.onStart,
105 + onComplete: exports.saucelabs.onComplete,
106 + onError: exports.saucelabs.onError,
107 + isDoneTimeout: exports.saucelabs.isDoneTimeout,
108 + desiredCapabilities: desiredCapabilities,
109 + };
110 + }
111 +
112 + return exports;
113 +}
grunt/config/webdriver-jasmine.js
+2 -78
@@ -2,11 +2,7 @@
2
3 var grunt = require('grunt');
4
5 -
6 -exports.local = {
7 - webdriver: {
8 - remote: { protocol: 'http:', hostname: '127.0.0.1', port: 9515, path: '/' }
9 - },
5 +module.exports = require('./webdriver-all')({
6 url: "http://127.0.0.1:9999/test/index.html",
7 onComplete: function(passed){
8 if (!passed){
@@ -16,76 +12,4 @@ exports.local = {
12 onError: function(error){
13 grunt.fatal(error);
14 }
19 -};
20 -
21 -if (grunt.option('debug')) {
22 - exports.local.url += '?debug=' + grunt.option('debug');
23 -}
24 -
25 -
26 -exports.saucelabs = {
27 - webdriver: {
28 - remote: {
29 - /* https://github.com/admc/wd/blob/master/README.md#named-parameters */
30 - user: process.env.SAUCE_USERNAME || 'React',
31 - pwd: process.env.SAUCE_ACCESS_KEY || '339d32ca-d594-4570-a3c2-94c50a91919b',
32 - protocol: 'http:',
33 - hostname: 'ondemand.saucelabs.com',
34 - port: '80',
35 - path: '/wd/hub'
36 - }
37 - },
38 - desiredCapabilities: {
39 - "build": process.env.TRAVIS_BUILD_NUMBER || 'dev' + Date.now(),
40 - "tunnel-identifier": process.env.TRAVIS_JOB_NUMBER || 'my awesome tunnel',
41 - "browserName": "chrome"
42 - },
43 - url: exports.local.url,
44 - onStart: function(browser){
45 - grunt.log.writeln("Starting WebDriver Test. Watch results here: http://saucelabs.com/tests/" + browser.sessionID);
46 - },
47 - onComplete: exports.local.onComplete,
48 - onError: exports.local.onError
49 -};
50 -
51 -/* https://saucelabs.com/docs/platforms */
52 -exports.saucelabs_ios =
53 -exports.saucelabs_ios6_1 = sauceItUp({ browserName: 'iphone', version: '6.1', platform:'OS X 10.8' });
54 -exports.saucelabs_ios6 = sauceItUp({ browserName: 'iphone', version: '6', platform:'OS X 10.8' });
55 -exports.saucelabs_ios5_1 = sauceItUp({ browserName: 'iphone', version: '5.1', platform:'OS X 10.8' });
56 -exports.saucelabs_ios5 = sauceItUp({ browserName: 'iphone', version: '5', platform:'OS X 10.6' });
57 -exports.saucelabs_ios4 = sauceItUp({ browserName: 'iphone', version: '4', platform:'OS X 10.6' });
58 -
59 -exports.saucelabs_ipad =
60 -exports.saucelabs_ipad6_1 = sauceItUp({ browserName: 'ipad', version: '6.1', platform:'OS X 10.8' });
61 -exports.saucelabs_ipad6 = sauceItUp({ browserName: 'ipad', version: '6', platform:'OS X 10.8' });
62 -exports.saucelabs_ipad5_1 = sauceItUp({ browserName: 'ipad', version: '5.1', platform:'OS X 10.8' });
63 -exports.saucelabs_ipad5 = sauceItUp({ browserName: 'ipad', version: '5', platform:'OS X 10.6' });
64 -exports.saucelabs_ipad4 = sauceItUp({ browserName: 'ipad', version: '4', platform:'OS X 10.6' });
65 -
66 -exports.saucelabs_android = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux' });
67 -exports.saucelabs_android_tablet = sauceItUp({ browserName: 'android', version: '4.0', platform:'Linux', 'device-type':'tablet' });
68 -
69 -exports.saucelabs_safari = sauceItUp({ browserName: 'safari' });
70 -exports.saucelabs_chrome = sauceItUp({ browserName: 'chrome' });
71 -exports.saucelabs_firefox = sauceItUp({ browserName: 'firefox' });
72 -
73 -exports.saucelabs_ie =
74 -exports.saucelabs_ie8 = sauceItUp({ browserName: 'internet explorer', version: 8 });
75 -exports.saucelabs_ie9 = sauceItUp({ browserName: 'internet explorer', version: 9 });
76 -exports.saucelabs_ie10 = sauceItUp({ browserName: 'internet explorer', version: 10 });
77 -exports.saucelabs_ie11 = sauceItUp({ browserName: 'internet explorer', version: 11, platform:'Windows 8.1' });
78 -
79 -
80 -function sauceItUp(desiredCapabilities) {
81 - desiredCapabilities["build"] = exports.saucelabs.desiredCapabilities["build"];
82 - desiredCapabilities["tunnel-identifier"] = exports.saucelabs.desiredCapabilities["tunnel-identifier"];
83 - return {
84 - webdriver: exports.saucelabs.webdriver,
85 - url: exports.saucelabs.url,
86 - onStart: exports.saucelabs.onStart,
87 - onComplete: exports.saucelabs.onComplete,
88 - onError: exports.saucelabs.onError,
89 - desiredCapabilities: desiredCapabilities,
90 - };
91 -}
15 +});
grunt/config/webdriver-perf.js new
+48
@@ -0,0 +1,48 @@
1 +'use strict';
2 +
3 +var grunt = require('grunt');
4 +
5 +var tests = grunt.file.expand(__dirname + '/../../perf/tests/*');
6 +
7 +var maxTime = 5;
8 +
9 +var reactVersions = [
10 + 'edge',
11 + 'previous'
12 +];
13 +
14 +var params = []
15 + .concat('headless=false')
16 + .concat('maxTime=' + maxTime)
17 + .concat(tests
18 + .map(function(path){ return path.split(/tests./i).reverse()[0]; })
19 + .map(encodeURIComponent)
20 + .map(function(filename){ return 'test=' + filename; })
21 + )
22 + .concat(reactVersions
23 + .map(encodeURIComponent)
24 + .map(function(version){ return 'react=' + version }
25 + )
26 +);
27 +
28 +module.exports = require('./webdriver-all')({
29 +
30 + url: "http://127.0.0.1:9999/perf/index.html?" + params.join('&'),
31 +
32 + isDoneTimeout: 15 * 60 * 1000,
33 +
34 + onStart: function(){
35 + grunt.event.on('perf results', function(results){
36 + console.log(results);
37 + });
38 + },
39 +
40 + onComplete: function(completedTestKeys){
41 + grunt.verbose.writeln('onComplete ' + JSON.stringify(completedTestKeys));
42 + },
43 +
44 + onError: function(error){
45 + grunt.fatal(error);
46 + }
47 +
48 +});
grunt/tasks/download-previous-version.js new
+47
@@ -0,0 +1,47 @@
1 +"use strict";
2 +
3 +var grunt = require('grunt');
4 +var http = require('http');
5 +var fs = require('fs');
6 +
7 +module.exports = function() {
8 + var completedSuccessfully = this.async();
9 + get(
10 + "http://react.zpao.com/builds/master/latest/react.min.js",
11 + __dirname + '/../../build/react-previous.min.js',
12 + function(success){
13 + if (!success) {
14 + return completedSuccessfully(success);
15 + }
16 + get(
17 + "http://react.zpao.com/builds/master/latest/JSXTransformer.js",
18 + __dirname + '/../../build/JSXTransformer-previous.js',
19 + completedSuccessfully
20 + );
21 + }
22 + );
23 +
24 + function get(url, targetFilePath, completedSuccessfully) {
25 + grunt.verbose.writeln('getting url "' + url + '"');
26 + http.get(url, function(response) {
27 + grunt.verbose.writeln('Received status code ' + response.statusCode + ' for "' + url + '"');
28 +
29 + if (response.statusCode != 200) {
30 + if (response.headers.location) {
31 + get(response.headers.location, targetFilePath);
32 + return;
33 + } else {
34 + grunt.fatal('Nothing else to do.');
35 + completedSuccessfully(false);
36 + return;
37 + }
38 + }
39 + grunt.verbose.writeln('Writing url to "' + targetFilePath + '"');
40 + response.pipe(fs.createWriteStream(targetFilePath))
41 + .on('close', function() {
42 + completedSuccessfully(true);
43 + })
44 + ;
45 + });
46 + }
47 +};
grunt/tasks/webdriver-all.js new
+73
@@ -0,0 +1,73 @@
1 +/* jshint evil: true */
2 +
3 +'use strict';
4 +
5 +var grunt = require("grunt");
6 +var wd = require('wd');
7 +
8 +module.exports = function task(getJSReport){
9 + var config = this.data;
10 + var taskSucceeded = this.async();
11 + getJSReport = getJSReport.bind(this, config, wd);
12 +
13 + var desiredCapabilities = {};
14 + if (config.desiredCapabilities) {
15 + Object.keys(config.desiredCapabilities).forEach(function(key) {
16 + if (config.desiredCapabilities[key] === undefined) {
17 + return;
18 + }
19 + desiredCapabilities[key] = config.desiredCapabilities[key];
20 + });
21 + }
22 + grunt.verbose.writeln("desiredCapabilities", JSON.stringify(desiredCapabilities));
23 +
24 + var browser = wd.promiseChainRemote(config.webdriver.remote);
25 +
26 + browser.on('status', function(info) {
27 + grunt.verbose.writeln(info);
28 + });
29 +
30 + browser.on('command', function(meth, path, data) {
31 + grunt.verbose.writeln(' > ' + meth, path, data || '');
32 + });
33 +
34 + var results = null;
35 +
36 + // browser._debugPromise();
37 + browser
38 + .init(desiredCapabilities)
39 + .then(config.onStart && config.onStart.bind(config, browser))
40 + .get(config.url)
41 + .then(function(){return browser;})
42 + .then(getJSReport)
43 + .then(function(data){ results = data; })
44 + .fail(function(error){
45 + grunt.log.error(error);
46 + return browser
47 + .eval('document.documentElement.innerText || document.documentElement.textContent')
48 + .then(grunt.verbose.writeln.bind(grunt.verbose))
49 + .then(function(){ throw error; })
50 + ;
51 + })
52 + .finally(function(){
53 + if (grunt.option('webdriver-keep-open')) {
54 + return;
55 + }
56 + grunt.verbose.writeln('Closing the browser window. To keep it open, pass the --webdriver-keep-open flag to grunt.');
57 + return browser.quit();
58 + })
59 + .done(
60 + function() {
61 + if (config.onComplete) {
62 + config.onComplete(results);
63 + }
64 + taskSucceeded(true);
65 + },
66 + function(error) {
67 + if (config.onError) {
68 + config.onError(error);
69 + }
70 + taskSucceeded(false);
71 + }
72 + );
73 +};
grunt/tasks/webdriver-jasmine.js
+10 -76
@@ -2,82 +2,16 @@
2
3 'use strict';
4
5 -var grunt = require("grunt");
6 -var wd = require('wd');
7 -
5 module.exports = function(){
9 - var config = this.data;
10 - var taskSucceeded = this.async();
11 -
12 - var desiredCapabilities = {};
13 - if (config.desiredCapabilities) {
14 - Object.keys(config.desiredCapabilities).forEach(function(key) {
15 - if (config.desiredCapabilities[key] === undefined) {
16 - return;
17 - }
18 - desiredCapabilities[key] = config.desiredCapabilities[key];
19 - });
20 - }
21 - grunt.verbose.writeln("desiredCapabilities", JSON.stringify(desiredCapabilities));
22 -
23 - var browser = wd.promiseChainRemote(config.webdriver.remote);
24 -
25 - browser.on('status', function(info) {
26 - grunt.verbose.writeln(info);
6 + return require('./webdriver-all').call(this, function(config, wd, browser){
7 + return browser
8 + .waitFor(wd.asserters.jsCondition("typeof window.jasmine != 'undefined'"), 5e3, 50)
9 + .fail(function(error){
10 + throw Error("The test page didn't load properly. " + error);
11 + })
12 + .waitFor(wd.asserters.jsCondition("typeof window.jasmine.getJSReport != 'undefined'"), 60e3, 100)
13 + .waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
14 + .eval("jasmine.getJSReport().passed")
15 + ;
16 });
28 -
29 - browser.on('command', function(meth, path, data) {
30 - grunt.verbose.writeln(' > ' + meth, path, data || '');
31 - });
32 -
33 - var results = null;
34 -
35 - // browser._debugPromise();
36 - browser
37 - .init(desiredCapabilities)
38 - .then(config.onStart && config.onStart.bind(config, browser))
39 - .get(config.url)
40 - .then(function(){return browser;})
41 - .then(getJSReport)
42 - .then(function(data){ results = data; })
43 - .fail(function(error){
44 - grunt.log.error(error);
45 - return browser
46 - .eval('document.documentElement.innerText || document.documentElement.textContent')
47 - .then(grunt.verbose.writeln.bind(grunt.verbose))
48 - .then(function(){ throw error; })
49 - ;
50 - })
51 - .finally(function(){
52 - if (grunt.option('webdriver-keep-open')) {
53 - return;
54 - }
55 - grunt.verbose.writeln('Closing the browser window. To keep it open, pass the --webdriver-keep-open flag to grunt.');
56 - return browser.quit();
57 - })
58 - .done(
59 - function() {
60 - if (config.onComplete) {
61 - config.onComplete(results);
62 - }
63 - taskSucceeded(true);
64 - },
65 - function(error) {
66 - if (config.onError) {
67 - config.onError(error);
68 - }
69 - taskSucceeded(false);
70 - }
71 - );
17 };
73 -
74 -function getJSReport(browser){
75 - return browser
76 - .waitFor(wd.asserters.jsCondition("typeof window.jasmine != 'undefined'"), 5e3, 50)
77 - .fail(function(error){
78 - throw Error("The test page didn't load properly. " + error);
79 - })
80 - .waitFor(wd.asserters.jsCondition("typeof window.jasmine.getJSReport != 'undefined'"), 60e3, 100)
81 - .waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
82 - .eval("jasmine.getJSReport().passed");
83 -}
grunt/tasks/webdriver-perf.js new
+23
@@ -0,0 +1,23 @@
1 +/* jshint evil: true */
2 +
3 +'use strict';
4 +
5 +var grunt = require('grunt');
6 +
7 +module.exports = function(){
8 + return require('./webdriver-all').call(this, function(config, wd, browser){
9 + if (!config.isDoneTimeout) {
10 + grunt.verbose.writeln('Expected isDoneTimeout config, using default value');
11 + }
12 + grunt.verbose.writeln('isDoneTimeout:' + config.isDoneTimeout);
13 + return browser
14 + .waitFor(wd.asserters.jsCondition("window.isDone === false"), 5e3, 50)
15 + .fail(function(error){
16 + throw Error("The test page didn't load properly. " + error);
17 + })
18 + .waitFor(wd.asserters.jsCondition("window.isDone === true"), config.isDoneTimeout || 30e3, 1e3)
19 + .waitFor(wd.asserters.jsCondition("window.postDataToURL.running <= 0"), 30e3, 500)
20 + .eval("window.completedTestKeys || window._unhandledError")
21 + ;
22 + });
23 +};
package.json
+3
@@ -38,6 +38,7 @@
38 "jstransform": "~2.0.1"
39 },
40 "devDependencies": {
41 + "benchmark": "~1.0.0",
42 "browserify": "~2.36.1",
43 "coverify": "~0.1.1",
44 "envify": "~1.0.1",
@@ -53,8 +54,10 @@
54 "grunt-contrib-jshint": "~0.7.2",
55 "gzip-js": "~0.3.2",
56 "jasmine-tapreporter": "~0.2.2",
57 + "microtime": "~0.5.1",
58 "optimist": "~0.6.0",
59 "phantomjs": "~1.9",
60 + "platform": "~1.0.0",
61 "populist": "~0.1.6",
62 "recast": "~0.5.6",
63 "sauce-tunnel": "~1.1.0",
perf/index.html new
+66
@@ -0,0 +1,66 @@
1 +<!doctype html>
2 +<meta charset=utf-8>
3 +<title>Perf Tests</title>
4 +
5 +<script>
6 +window.onerror = function(error){
7 + window._unhandledError = error;
8 + window.isDone = true;
9 +}
10 +</script>
11 +
12 +<script src="./lib/perf-test-runner.browser.js"></script>
13 +<script> perfRunner.Polyfill(); </script>
14 +<script src="../build/react.js"></script>
15 +<script src="../build/JSXTransformer.js"></script>
16 +<script src="../test/lib/postDataToURL.browser.js"></script>
17 +<script src="./lib/BrowserPerfRunnerContext.react.js"></script>
18 +<script src="./lib/BrowserPerfRunnerApp.react.js"></script>
19 +
20 +<script>
21 +
22 +var tests = [
23 + "sanity.js",
24 + "todolist-mount.js",
25 + "todolist-edit.js",
26 + "todolist-add.js",
27 + "todolist-do-stuff.js",
28 + "setState-callback-5.js",
29 + "setState-callback.js",
30 + "basic-div.js",
31 + "basic-unmount.js",
32 + "renderComponent-basic.js",
33 + "shouldComponentUpdate.js",
34 +];
35 +
36 +var reactVersions = [
37 + 'edge',
38 + 'builds/master/latest'
39 +];
40 +
41 +window.onload = function(){
42 + window.isDone = false;
43 +
44 + React.renderComponent(
45 + BrowserPerfRunnerApp({
46 + headless: perfRunner.getQueryParamArrayOrDefault('headless', [false])[0],
47 + react: perfRunner.getQueryParamArrayOrDefault('react', reactVersions),
48 + tests: perfRunner.getQueryParamArrayOrDefault('test', tests),
49 + maxTime: perfRunner.getQueryParamArrayOrDefault('maxTime', [5])[0],
50 + onCompleteEach: function(results){
51 + console.log('onCompleteEach', results);
52 + postDataToURL({type:'perf', message:results}, '/reportTestResults');
53 + },
54 + onError: function(error){
55 + window._unhandledError = error;
56 + },
57 + onComplete: function(results){
58 + window.completedTestKeys = Object.keys(results);
59 + window.isDone = true;
60 + }
61 + }),
62 + document.body
63 + );
64 +}
65 +
66 +</script>
perf/lib/BrowserPerfRunnerApp.react.js new
+205
@@ -0,0 +1,205 @@
1 +var BrowserPerfRunnerApp = React.createClass({
2 +
3 + propTypes: {
4 + tests: React.PropTypes.array.isRequired,
5 + react: React.PropTypes.array.isRequired,
6 + maxTime: React.PropTypes.number,
7 + onCompleteEach: React.PropTypes.func,
8 + onComplete: React.PropTypes.func,
9 + onError: React.PropTypes.func,
10 + headless: React.PropTypes.bool
11 + },
12 +
13 + getInitialState: function(){
14 + var queue = [];
15 + this.props.tests.forEach(function(testName){
16 + this.props.react.forEach(function(version){
17 + queue.push({
18 + test: testName,
19 + react: version
20 + });
21 + },this);
22 + },this);
23 + return {
24 + queue: queue,
25 + results: {}
26 + };
27 + },
28 +
29 + handleResults: function(results){
30 + this.state.results[results.test + '@' + results.react] = results;
31 + this.replaceState(this.state);
32 + },
33 +
34 + handleComplete: function(queueItem){
35 + queueItem.completed = true;
36 +
37 + if (!this.props.onCompleteEach) {
38 + return;
39 + }
40 + // Can't get the resultsForAllVersions if there are still some queued
41 + var incompleteCount = 0;
42 + for (var index = this.state.queue.length; --index >= 0;){
43 + if (this.state.queue[index].completed) {
44 + continue;
45 + }
46 + if (this.state.queue[index].test === queueItem.test) {
47 + return;
48 + }
49 + incompleteCount ++;
50 + }
51 + var resultsForAllVersions = Object.keys(this.state.results)
52 + .filter(function(key){return key.indexOf(queueItem.test) === 0;})
53 + .map(function(key){return this.state.results[key];}, this)
54 + ;
55 + this.props.onCompleteEach(resultsForAllVersions);
56 +
57 + if (this.props.onComplete && incompleteCount === 0) {
58 + this.props.onComplete(this.state.results);
59 + }
60 + },
61 +
62 + render: function(){
63 + var grid = null;
64 +
65 + if (!this.props.headless) {
66 + grid = GridViewTable({
67 + rows: this.props.tests,
68 + cols: this.props.react,
69 + renderCell: BrowserPerfRunnerApp.renderBenchmarkCell,
70 + value: this.state.results
71 + });
72 + }
73 +
74 + return React.DOM.div(null,
75 + BenchmarkQueue({
76 + initialQueue: this.state.queue,
77 + onChange: this.handleResults,
78 + maxTime: this.props.maxTime,
79 + onCompleteEach: this.handleComplete,
80 + onError: this.props.onError
81 + }),
82 + grid
83 + );
84 + }
85 +});
86 +
87 +BrowserPerfRunnerApp.renderBenchmarkCell = function(props, row, col){
88 + if (col == null && row == null) return React.DOM.th(null);
89 + if (row == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'center'}}, col);
90 +
91 + var benchmarks = Object.keys(props.value)
92 + .filter(function(key){
93 + return key.indexOf(row) === 0;
94 + })
95 + .map(function(key){
96 + return props.value[key];
97 + })
98 + .filter(function(benchmark){
99 + return benchmark && !benchmark.isRunning && benchmark.stats;
100 + })
101 + ;
102 +
103 + if (col == null) return React.DOM.th({style:{verticalAlign:'top', textAlign:'right'}},
104 + React.DOM.a({href:'?test=' + row}, benchmarks[0] && benchmarks[0].name || row)
105 + );
106 +
107 + var key = row + '@' + col;
108 + var benchmark = props.value[key];
109 + if (!(benchmark && benchmark.stats)) return React.DOM.td({key:key});
110 +
111 +
112 + var colors = [
113 + '000000',
114 + 'AA0000',
115 + '00AA00',
116 + 'AA5500',
117 + '0000AA',
118 + 'AA00AA',
119 + '00AAAA',
120 + 'AAAAAA',
121 +
122 + '555555',
123 + 'FF5555',
124 + '55FF55',
125 + 'FFFF55',
126 + '5555FF',
127 + 'FF55FF',
128 + '55FFFF',
129 + 'FFFFFF'
130 + ];
131 +
132 + function chartValue(value){
133 + return Math.round(valueFromRangeToRange(value, chartValue.min, chartValue.max, 0, 100));
134 + }
135 + chartValue.min = Math.min.apply(Math, benchmarks.map(function(benchmark){return Math.min.apply(Math, benchmark.stats.sample);}));
136 + chartValue.max = Math.max.apply(Math, benchmarks.map(function(benchmark){return Math.max.apply(Math, benchmark.stats.sample);}));
137 +
138 + var means = benchmarks.map(function(benchmark){
139 + return benchmark.stats.mean;
140 + });
141 + benchmarks.forEach(function(benchmark){
142 + benchmark.isTheWinner = benchmark.stats.mean <= Math.min.apply(Math, means);
143 + });
144 +
145 + var chartValues = benchmarks.map(function(benchmark){
146 + // benchmark.stats.sample.sort(function(a,b){return b - a;});
147 + return benchmark.stats.sample.map(chartValue).join(',');
148 + }).join('|');
149 +
150 + return (
151 + React.DOM.td({key:key, style:{textAlign:'center', width:234, verticalAlign:'top'}},
152 + benchmark.error && benchmark.error.message || '',
153 + React.DOM.div({style: benchmark.isTheWinner ? { backgroundColor:'#0A5', color:'#AFA' } : {backgroundColor:'transparent', color:'inherit'}},
154 + Math.round(1 / benchmark.stats.mean * 100) / 100, " op/s ",
155 + React.DOM.strong(null, Math.round(benchmark.stats.mean * 1000 * 100) / 100, " ms/op "),
156 + React.DOM.small(null, "(±" + (Math.round(benchmark.stats.rme * 10) / 10) + "%)")
157 + ),
158 + benchmark.isRunning && 'Running' || React.DOM.img({
159 + style: {
160 + borderWidth: 2,
161 + borderStyle: 'solid',
162 + color: '#' + colors[benchmarks.indexOf(benchmark)]
163 + },
164 + width: 230,
165 + height: 50,
166 + src: 'https://chart.googleapis.com/chart?cht=ls&chs=460x100&chd=t:' + chartValues + '&chco=' + colors.join(',')
167 + })
168 + )
169 + );
170 +}
171 +
172 +function valueFromRangeToRange(value, fromMin, fromMax, toMin, toMax){
173 + var fromRange = fromMax - fromMin;
174 + var toRange = toMax - toMin;
175 + return (((value - fromMin) * toRange) / fromRange) + toMin;
176 +}
177 +
178 +var GridViewTable = React.createClass({
179 +
180 + propTypes: {
181 + rows: React.PropTypes.array.isRequired,
182 + cols: React.PropTypes.array.isRequired,
183 + renderCell: React.PropTypes.func.isRequired
184 + },
185 +
186 + _renderCell: function(col){
187 + return this.props.renderCell({ value:this.props.value }, this._row, col);
188 + },
189 +
190 + _renderRow: function(row){
191 + this._row = row;
192 + return React.DOM.tr({key:row},
193 + this._renderCell(null, 0),
194 + this.props.cols.map(this._renderCell, this)
195 + );
196 + },
197 +
198 + render: function(){
199 + return React.DOM.table(null,
200 + this._renderRow(null, 0),
201 + this.props.rows.map(this._renderRow, this)
202 + );
203 + }
204 +
205 +});
perf/lib/BrowserPerfRunnerContext.react.js new
+195
@@ -0,0 +1,195 @@
1 +var BenchmarkQueue = React.createClass({
2 + propTypes: {
3 + debug: React.PropTypes.bool,
4 + onChange: React.PropTypes.func.isRequired,
5 + initialQueue: React.PropTypes.array.isRequired,
6 + maxTime: React.PropTypes.number,
7 + onCompleteEach: React.PropTypes.func,
8 + onError: React.PropTypes.func
9 + },
10 +
11 + getDefaultProps: function(){
12 + return {
13 + maxTime: 5
14 + };
15 + },
16 +
17 + getInitialState: function(){
18 + return {
19 + queue: this.props.initialQueue.slice()
20 + };
21 + },
22 +
23 + setItemState: function(state){
24 + state.test = this.state.queue[0].test;
25 + state.react = this.state.queue[0].react;
26 + this.props.onChange(state);
27 + },
28 +
29 + handleContextReady: function(window){
30 + var benchmark = window.Benchmark(window.exports);
31 + benchmark.options.maxTime = this.props.maxTime; //DEBUG
32 +
33 + var itemState = {
34 + testRunnerURL: window.location.href,
35 +
36 + name: window.exports.name,
37 + platform: window.Benchmark.platform.description,
38 + reactVersion: window.React.version,
39 +
40 + isMinified: (function(){
41 + var code = window.React.renderComponent.toString();
42 + return code.indexOf(',') - code.indexOf('(') <= 2;
43 + }())
44 + };
45 +
46 + this.setItemState(itemState);
47 +
48 + var self = this;
49 + benchmark.on('start error cycle complete', function(){
50 + var stats = JSON.parse(JSON.stringify(benchmark.stats));
51 + itemState.stats = stats;
52 + itemState.isRunning = benchmark.running;
53 + itemState.error = benchmark.error;
54 + self.setItemState(itemState);
55 + });
56 + if (this.props.onError) {
57 + benchmark.on('error', this.props.onError);
58 + }
59 + benchmark.on('complete', function(){
60 + var queue = self.state.queue.slice();
61 + var queueItem = queue.shift();
62 + if (self.props.onCompleteEach) {
63 + self.props.onCompleteEach(queueItem);
64 + }
65 + self.setState({ queue:queue });
66 + });
67 + benchmark.run({async:true});
68 + },
69 +
70 + shouldComponentUpdate: function(nextProps, nextState){
71 + return nextState.queue.length < this.state.queue.length;
72 + },
73 +
74 + render: function(){
75 + if (!(this.state.queue && this.state.queue.length > 0)){
76 + return React.DOM.div({style:{display:'none'}});
77 + }
78 + return BrowserPerfRunnerContext({
79 + debug: this.props.debug,
80 + test: this.state.queue[0].test,
81 + react: this.state.queue[0].react,
82 + onReady: this.handleContextReady
83 + });
84 + }
85 +});
86 +
87 +var BrowserPerfRunnerContext = React.createClass({
88 +
89 + propTypes: {
90 + debug: React.PropTypes.bool,
91 + test: function(object, key){
92 + React.PropTypes.string.isRequired(object, key);
93 + if (/\.jsx?$/i.test(object[key])) return;
94 + throw Error('Expected `' + key + '` to be a test file name with extension `.js` or `.jsx`');
95 + },
96 + react: function(object, key){
97 + React.PropTypes.string.isRequired(object, key);
98 + if (/^(?:builds\/.+|edge|previous|(?:\d+\.){2}\d+)$/.test(object[key])) return;
99 + throw Error('Expected `' + key + '` prop to be a valid react version string, build string or "edge" or "previous"');
100 + },
101 + onReady: React.PropTypes.func.isRequired
102 + },
103 +
104 + getInitialState: function(){
105 + return {
106 + testRunnerURL:'about:blank'
107 + };
108 + },
109 +
110 + // _handleFrameError: function(error){
111 + // console.error('BrowserPerfRunnerContext', error);
112 + // },
113 + //
114 + // _handleFrameLoad: function(event){
115 + // console.log('BrowserPerfRunnerContext', event);
116 + // },
117 + //
118 + _handleMessage: function(event){
119 + if (location.href.indexOf(event.origin) !== 0)
120 + return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message from ' + event.origin);
121 + if (event.source.location.href.indexOf(this.state.testRunnerURL) === -1)
122 + return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message from ' + event.source.location.href);
123 + if (event.data !== 'Ready!')
124 + return console.debug('BrowserPerfRunnerContext#_handleMessage ignored message ' + JSON.stringify(event.data));
125 +
126 + this.props.onReady(event.source);
127 + },
128 +
129 + _getTestRunnerURL: function(props){
130 + return 'runner.html' +
131 + '?' +
132 + 'debug=' + (props.debug ? 1 : 0) +
133 + '&' +
134 + 'react=' + encodeURIComponent(props.react) +
135 + '&' +
136 + 'test=' + encodeURIComponent(props.test)
137 + },
138 +
139 + _renderState: function(props){
140 + return {
141 + testRunnerURL: this._getTestRunnerURL(props)
142 + };
143 + },
144 +
145 + componentDidMount: function(){
146 + var node = this.refs.iframe.getDOMNode();
147 + // node.onload = this._handleFrameLoad;
148 + // node.onerror = this._handleFrameError;
149 + if (window.addEventListener) {
150 + window.addEventListener('message', this._handleMessage, false);
151 + } else if (window.attachEvent) {
152 + window.attachEvent('onmessage', this._handleMessage);
153 + } else {
154 + throw Error('cannot attach onmessage listener');
155 + }
156 + this.setState(this._renderState(this.props));
157 + },
158 +
159 + componentWillUnmount: function(){
160 + if (window.removeEventListener) {
161 + window.removeEventListener('message', this._handleMessage);
162 + } else if (window.detachEvent) {
163 + window.detachEvent('onmessage', this._handleMessage);
164 + } else {
165 + throw Error('cannot detach onmessage listener');
166 + }
167 + this.refs.iframe.getDOMNode().src = '';
168 + },
169 +
170 + componentWillReceiveProps: function(nextProps){
171 + this.setState(this._renderState(nextProps));
172 + },
173 +
174 + shouldComponentUpdate: function(nextProps, nextState){
175 + return nextState.testRunnerURL != this.state.testRunnerURL;
176 + },
177 +
178 + render: function(){
179 + return (
180 + React.DOM.iframe({
181 + ref: 'iframe',
182 + name: "BrowserPerfRunnerContextFrame",
183 + style: this.style,
184 + src: this.state.testRunnerURL
185 + })
186 + );
187 + },
188 +
189 + style: {
190 + position: 'absolute',
191 + right: '100%',
192 + bottom: '100%'
193 + }
194 +
195 +});
perf/lib/perf-test-runner.browser.js new
+204
@@ -0,0 +1,204 @@
1 +if (typeof console == 'undefined') console = {
2 + log: function(){},
3 + warn: function(){},
4 + error: function(){},
5 + debug: function(){}
6 +};
7 +
8 +var perfRunner;
9 +if (typeof exports == 'object') {
10 + perfRunner = exports;
11 +} else {
12 + perfRunner = {};
13 +}
14 +
15 +perfRunner.assert = function(test, message){
16 + if (typeof test == 'function') test = test();
17 + if (test) return;
18 + throw Error(message);
19 +}
20 +
21 +perfRunner.WriteScript = function(props){
22 + var type = '';
23 + if (props.jsx) {
24 + type = ' type="text/jsx"';
25 + }
26 + var src = props.src;
27 + if (!props.cache) {
28 + src += src.indexOf('?') === -1 ? '?_' : '&_';
29 + src += perfRunner.WriteScript.cacheBust;
30 + }
31 + document.write('<script' + type + ' src="' + src + '"><\/script>');
32 +}
33 +perfRunner.WriteScript.cacheBust = (+new Date).toString(36);
34 +
35 +perfRunner.WriteReactLibScript = function(params){
36 + var src;
37 + var minSuffix;
38 + if (params.debug) {
39 + minSuffix = '';
40 + } else {
41 + minSuffix = '.min';
42 + }
43 +
44 + if (params.version && typeof params.version != 'string') throw TypeError("Expected 'version' to be a string");
45 +
46 + if (params.version == 'edge' || !params.version) {
47 + console.log('React edge (local)');
48 + perfRunner.WriteScript({src:'../build/react' + minSuffix + '.js'});
49 + perfRunner.WriteScript({src:'../build/JSXTransformer.js'});
50 + } else if (params.version == 'previous') {
51 + console.log('React previous (local)');
52 + perfRunner.WriteScript({cache:true, src:'../build/react-previous' + minSuffix + '.js'});
53 + perfRunner.WriteScript({cache:true, src:'../build/JSXTransformer-previous.js'});
54 + } else if (params.version.indexOf('builds/') === 0) {
55 + perfRunner.WriteScript({cache:true, src:'http://react.zpao.com/' + params.version + '/react' + minSuffix + '.js'});
56 + perfRunner.WriteScript({cache:true, src:'http://react.zpao.com/' + params.version + '/JSXTransformer.js'});
57 + } else {
58 + console.log('React ' + params.version);
59 + perfRunner.WriteScript({cache:true, src:'http://fb.me/react-' + params.version + minSuffix + '.js'});
60 + perfRunner.WriteScript({cache:true, src:'http://fb.me/JSXTransformer-' + params.version + '.js'});
61 + }
62 + if (params.debug) {
63 + console.warn('Loading the unminified build of React, performance may suffer.');
64 + console.warn('Load "' + location.href.replace(/\bdebug=\w+&?|&\bdebug=\w+/ig, '') + '" for better perf.');
65 + } else {
66 + console.warn('Loading the minified build of React, debugging may be harder.');
67 + console.warn('Load "' + location.href.replace(/\bdebug=\w+&?|&\bdebug=\w+/ig, '') + '&debug=1' + '" for easier debugging.');
68 + }
69 +}
70 +
71 +perfRunner.WriteTestScript = function(params){
72 + if (Array.isArray(params.test)) {
73 + return params.test
74 + .map(function(test){return {test:test};})
75 + .map(perfRunner.WriteTestScript)
76 + ;
77 + }
78 + perfRunner.assert(params.test.indexOf('..') === -1, 'no relative paths allowed');
79 + return perfRunner.WriteScript({jsx:true, src: './tests/' + params.test});
80 +}
81 +
82 +perfRunner.getQueryParamArray = function(key){
83 + var values;
84 + var queryString = location.search.substr(1);
85 + var _key = encodeURIComponent(key) + '=';
86 +
87 + if (queryString.indexOf(_key) > -1) {
88 + values = queryString
89 + .split(_key)
90 + .slice(1)
91 + .map(function(part){return part.split('&')[0];})
92 + .map(decodeURIComponent)
93 + .map(function(string){
94 + try {
95 + return JSON.parse(string);
96 + } catch(e){}
97 + return string;
98 + })
99 + ;
100 + }
101 +
102 + perfRunner.assert(values && values.length && values[0], 'expected ' + key + ' query param');
103 + return values;
104 +}
105 +
106 +perfRunner.getQueryParamArrayOrDefault = function(key, defaultValue){
107 + try {
108 + return perfRunner.getQueryParamArray(key);
109 + } catch (e) {}
110 + return defaultValue;
111 +}
112 +
113 +perfRunner.Polyfill = function(){
114 + if (typeof Function.prototype.bind != 'undefined') return;
115 + perfRunner.WriteScript({src:'/node_modules/es5-shim/es5-shim.js', cache:true});
116 + perfRunner.WriteScript({src:'/node_modules/es5-shim/es5-sham.js', cache:true});
117 +}
118 +
119 +perfRunner.BenchmarkResults = function(props){
120 + return perfRunner.roundNumberWithPrecision(props.stats.mean * 1000) + 'ms/op'
121 +}
122 +
123 +perfRunner.roundNumberWithPrecision = function(number, precision){
124 + if (!precision) precision = 1000;
125 + return Math.round(number * precision) / precision;
126 +}
127 +
128 +perfRunner.quickBench = function(benchmarkOptions, onComplete, onBeforeStart){
129 + var bench = new Benchmark(benchmarkOptions);
130 + if (onBeforeStart) onBeforeStart(null, bench);
131 +
132 + bench.on('error', function(event){
133 + console.error(event.message);
134 + console.log(event.target.compiled.toString());
135 + onComplete(Error(event.error));
136 + });
137 +
138 + bench.on('start', function(){
139 + console.log('starting', bench.name);
140 + });
141 +
142 + bench.on('cycle', function(){
143 + var bench = this,
144 + size = bench.stats.size;
145 +
146 + if (!bench.aborted) {
147 + console.warn(bench.name + ' × ' + bench.count +
148 + ' (' + bench.stats.sample.length + ' samples)' +
149 + ' (' + Math.round(1 / bench.stats.mean) + ' ops/sec' + ')' +
150 + ' (' + (bench.stats.mean * 1000) + ' ms/op' + ')' +
151 + ' (±' + bench.stats.rme.toFixed(2) + '%)' +
152 + ' with ' + React.version
153 + );
154 + }
155 + });
156 +
157 + bench.on('complete', function(){
158 + var results = {
159 + platform: Benchmark.platform.description,
160 + react: React.version,
161 + name: bench.name,
162 + // times: bench.times,
163 + // stats: bench.stats
164 + };
165 +
166 + results['s/op'] = bench.stats.mean
167 + results['ms/op'] = results['s/op'] * 1000
168 + results['op/s'] = 1 / results['s/op']
169 + results["% frame 60"] = results['ms/op'] / (1000 / 60) * 100
170 +
171 + console.log(results);
172 + onComplete(null, results);
173 + });
174 +
175 + bench.run();
176 +};
177 +
178 +perfRunner.singleTest = function(benchmarkOptions, onComplete){
179 + var bench = Benchmark(exports);
180 + bench.on('complete', function(){
181 + var results = {
182 + platform: Benchmark.platform.description,
183 + react: React.version,
184 + name: bench.name,
185 + stats: bench.stats
186 + };
187 + onComplete(results);
188 + });
189 + bench.run();
190 +}
191 +
192 +perfRunner.ViewObject = function(props){
193 + var value = props.value;
194 + delete props.value;
195 +
196 + if (typeof value != 'object') return React.DOM.span(props, [JSON.stringify(value), " ", typeof value]);
197 +
198 + return React.DOM.table(props, Object.keys(value).map(function(key){
199 + return React.DOM.tr(null,
200 + React.DOM.th(null, key),
201 + React.DOM.td(null, perfRunner.ViewObject({key:key, value:value[key]}))
202 + );
203 + }));
204 +}
perf/lib/todolist.browser.js new
+117
@@ -0,0 +1,117 @@
1 +/*global*/todolist = {};
2 +
3 +todolist.ID = Date.now();
4 +
5 +todolist.now = window.performance && window.performance.now && window.performance.now.bind(window.performance) || Date.now.bind(Date);
6 +
7 +todolist.App = React.createClass({
8 + propTypes: {
9 + fakeDataCount: React.PropTypes.number
10 + },
11 +
12 + getInitialState: function(){
13 + var todos;
14 + if (this.props.fakeDataCount) {
15 + todos = Array(this.props.fakeDataCount + 1).join(',').split(',').map(function(ignore, index){
16 + return {id:index, title:"Title " + index + " " + Math.random().toString(36).substring(2,16), completed:!!(index % 2)};
17 + });
18 + }
19 + return {
20 + timerStart: todolist.now(),
21 + timerEnd: null,
22 + timerEvent: 'getInitialState',
23 + todos: todos || this.props.initialData || []
24 + };
25 + },
26 + componentWillUpdate: function(props, state){
27 + state.todos = state.todos.filter(function(todo){
28 + return !todo.deleted;
29 + });
30 + },
31 + addItem: function(title, callback){
32 + if (title == null || title === '') {
33 + var error = Error('invalid title');
34 + if (!callback) throw error;
35 + return callback(error);
36 + }
37 + var todos = this.state.todos.slice();
38 + var todo = {
39 + id: ++todolist.ID,
40 + title: title,
41 + completed: false
42 + };
43 + todos.push(todo);
44 + if (callback) callback = callback.bind(this, todo);
45 + this.setState({ timerEvent:'addItem', timerStart:todolist.now(), timerEnd:null, todos:todos }, callback);
46 + return todo;
47 + },
48 + deleteItemById: function(id, callback){
49 + var todo = this._getById(id);
50 + if (!todo) return callback && callback(Error('todo with id ' + id + ' not found'));
51 + todo.deleted = true;
52 + this.setState({ timerEvent:'deleteItemById', timerStart:todolist.now(), timerEnd:null, todos:this.state.todos }, callback);
53 + },
54 + setItemCompleted: function(id, completed, callback){
55 + var todo = this._getById(id);
56 + if (!todo) return callback && callback(Error('todo with id ' + id + ' not found'));
57 + todo.completed = completed;
58 + this.setState({ timerEvent:'setItemCompleted', timerStart:todolist.now(), timerEnd:null, todos:this.state.todos }, callback);
59 + },
60 + _getById: function(id){
61 + id = +id;
62 + var todos = this.state.todos;
63 + for (var index = todos.length; --index >= 0;){
64 + if (todos[index].id === id) return todos[index];
65 + }
66 + return null;
67 + },
68 + _handleItemCompletedCheckboxChange: function(event){
69 + var node = event.target;
70 + this.setItemCompleted(node.value, node.checked);
71 + },
72 + _handleItemDeletedButton: function(event){
73 + var node = event.target;
74 + this.deleteItemById(node.value);
75 + },
76 + _renderTodoItem: function(todo, index){
77 + return (
78 + React.DOM.li({key:todo.id},
79 + React.DOM.button({value:todo.id, onClick:this._handleItemDeletedButton}, 'x'),
80 + React.DOM.input({type:"checkbox", value:todo.id, checked:todo.completed, onChange:this._handleItemCompletedCheckboxChange}),
81 + " ",
82 + React.DOM.span({style:{"text-decoration":todo.completed ? "line-through" : ""}}, todo.title)
83 + )
84 + );
85 + },
86 + render: function(){
87 + if (!this.state.timerEnd) this.state.timerEnd = todolist.now();
88 + if (this.props.onRender) this.props.onRender();
89 + return (
90 + React.DOM.div(null,
91 + React.DOM.h1(null, "TODO"),
92 + React.DOM.h3(null, this.state.timerEvent, " ", this.state.timerEnd - this.state.timerStart, 'ms'),
93 + todolist.NewItemForm({onEnter:this.addItem, autoFocus:true}),
94 + React.DOM.ol(null, this.state.todos.map(this._renderTodoItem))
95 + )
96 + );
97 + },
98 + componentDidMount: function(rootNode){
99 + if (this.props.onDidMount) this.props.onDidMount(rootNode);
100 + }
101 +});
102 +
103 +todolist.NewItemForm = React.createClass({
104 + _handleNewItemKeyDown: function(event){
105 + if (event.which !== 13/*enter key*/) return;
106 + var node = this.refs.text.getDOMNode();
107 + var value = node.value;
108 + node.value = '';
109 + this.props.onEnter(value);
110 + return false;
111 + },
112 + render: function(){
113 + return this.transferPropsTo(
114 + React.DOM.input({ref:"text", onKeyDown:this.props.onEnter && this._handleNewItemKeyDown})
115 + );
116 + }
117 +});
perf/lib/todolist.html new
+16
@@ -0,0 +1,16 @@
1 +<!doctype html>
2 +<meta charset=utf-8>
3 +<title>todolist</title>
4 +
5 +<script src="perf-test-runner.browser.js"></script>
6 +<script> perfRunner.Polyfill(); </script>
7 +<script src="../../build/react.min.js"></script>
8 +<script src="todolist.browser.js"></script>
9 +
10 +<script>
11 +function main(){
12 + var app = todolist.App({ fakeDataCount:333 });
13 + React.renderComponent(app, document.body);
14 +}
15 +window.onload = main;
16 +</script>
perf/runner.html new
+59
@@ -0,0 +1,59 @@
1 +<!doctype html>
2 +<meta charset=utf-8>
3 +<title>Perf Test Runner</title>
4 +
5 +<script src="../node_modules/platform/platform.js"></script>
6 +<script src="../node_modules/benchmark/benchmark.js"></script>
7 +<script src="lib/perf-test-runner.browser.js"></script>
8 +
9 +<script> perfRunner.Polyfill(); </script>
10 +
11 +<script>
12 +
13 +perfRunner.WriteReactLibScript({
14 + version: perfRunner.getQueryParamArrayOrDefault('react', [null])[0],
15 + debug: perfRunner.getQueryParamArrayOrDefault('debug', [false])[0]
16 +});
17 +
18 +perfRunner.WriteScript({src:"lib/todolist.browser.js"});
19 +
20 +perfRunner.WriteTestScript({ test:perfRunner.getQueryParamArray('test') });
21 +
22 +</script>
23 +
24 +<script>
25 +
26 +function checkIfReady(){
27 + if (!window.exports) return setTimeout(checkIfReady, 50);
28 +
29 + console.timeStamp && console.timeStamp(perfRunner.getQueryParamArray('test'));
30 + if (typeof gc == 'function') gc();
31 + window.parent.postMessage(perfRunner.getQueryParamArrayOrDefault('callback', ['Ready!'])[0], '*');
32 +}
33 +checkIfReady();
34 +
35 +function main(callback){
36 + perfRunner.singleTest(exports, function(results){
37 + document.getElementById('results').textContent = JSON.stringify(results, null, 2);
38 + callback();
39 + });
40 +}
41 +
42 +function runOnce(callback){
43 + setTimeout(function(){
44 + exports.fn({resolve: function(){
45 + setTimeout(callback, 0);
46 + }});
47 + },0);
48 +}
49 +
50 +</script>
51 +<button autofocus onclick="if (disabled) return false; main(function(){ disabled=false; textContent='Run again!'; }); textContent='Running!'; disabled=true">Run!</button>
52 +<div>
53 + <button onclick="exports.setup();">Setup!</button>
54 + <button onclick="if (disabled) return false; runOnce(function(){ disabled=false; textContent='Run again!'; }); textContent='Running!'; disabled=true">Run once!</button>
55 + <button onclick="exports.teardown();">Teardown!</button>
56 +</div>
57 +
58 +<pre id=results style="font-size:9px"></pre>
59 +
perf/tests/basic-div.js new
+16
@@ -0,0 +1,16 @@
1 +/* jshint undef: true, unused: true */
2 +
3 +/* global document */
4 +/* global window */
5 +/* global Benchmark */
6 +/* global React */
7 +
8 +if (typeof exports == 'undefined') exports = {};
9 +
10 +/*http://benchmarkjs.com/docs#options*/
11 +
12 +exports.name = 'React.DOM.div, no props';
13 +
14 +exports.fn = function(){
15 + React.DOM.div(null, 'lol, perf testing ', this.count);
16 +};
perf/tests/basic-unmount.js new
+23
@@ -0,0 +1,23 @@
1 +/* jshint undef: true, unused: true */
2 +
3 +/* global document */
4 +/* global window */
5 +/* global Benchmark */
6 +/* global React */
7 +
8 +if (typeof exports == 'undefined') exports = {};
9 +
10 +/*http://benchmarkjs.com/docs#options*/
11 +
12 +exports.name = 'unmountComponentAtNode';
13 +
14 +exports.setup = function(){
15 + /*global*/_rootNode = document.createElement('div');
16 + document.body.appendChild(_rootNode);
17 + var _firstChild = React.DOM.div(null, 'lol, perf testing ', this.count);
18 + React.renderComponent(_firstChild, _rootNode);
19 +};
20 +exports.fn = function(){
21 + if (React.unmountAndReleaseReactRootNode) React.unmountAndReleaseReactRootNode(_rootNode);
22 + else React.unmountComponentAtNode(_rootNode);
23 +};
perf/tests/renderComponent-basic.js new
+24
@@ -0,0 +1,24 @@
1 +/* jshint undef: true, unused: true */
2 +
3 +/* global document */
4 +/* global window */
5 +/* global Benchmark */
6 +/* global React */
7 +
8 +if (typeof exports == 'undefined') exports = {};
9 +
10 +/*http://benchmarkjs.com/docs#options*/
11 +
12 +exports.name = 'React.renderComponent single div';
13 +
14 +exports.setup = function(){
15 + /*global*/_rootNode = document.createElement('div');
16 + document.body.appendChild(_rootNode);
17 +};
18 +exports.fn = function(){
19 + React.renderComponent(React.DOM.div(null, 'lol, perf testing ', this.count), _rootNode);
20 +};
21 +exports.teardown = function(){
22 + if (React.unmountAndReleaseReactRootNode) React.unmountAndReleaseReactRootNode(_rootNode);
23 + else React.unmountComponentAtNode(_rootNode);
24 +};
perf/tests/sanity.js new
+15
@@ -0,0 +1,15 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'Trivial benchmark to verify that everything works';
6 +
7 +exports.setup = function(){
8 + var foo;
9 +};
10 +exports.fn = function(){
11 + foo = Array(999).join('Howdy!\n');
12 +};
13 +exports.teardown = function(){
14 + foo = null;
15 +};
perf/tests/setState-callback-5.js new
+41
@@ -0,0 +1,41 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'From setState to callback (x5)';
6 +
7 +exports.defer = true;
8 +
9 +exports.setup = function(){
10 + /*global*/_rootNode = document.createElement('div');
11 + document.body.appendChild(_rootNode);
12 + /*global*/setState = null;
13 +
14 + var AwesomeComponent = React.createClass({
15 + getInitialState: function(){
16 + return { random:null };
17 + },
18 + render: function(){
19 + if (!setState) setState = this.setState.bind(this);
20 + return React.DOM.div(null, this.state.random);
21 + }
22 + });
23 +
24 + React.renderComponent(AwesomeComponent(null), _rootNode);
25 +};
26 +exports.fn = function(deferred){
27 + setState({random: Date.now() + Math.random()}, function(){
28 + setState({random: Date.now() + Math.random()}, function(){
29 + setState({random: Date.now() + Math.random()}, function(){
30 + setState({random: Date.now() + Math.random()}, function(){
31 + setState({random: Date.now() + Math.random()}, function(){
32 + deferred.resolve();
33 + });
34 + });
35 + });
36 + });
37 + });
38 +};
39 +exports.teardown = function(){
40 + React.unmountComponentAtNode(_rootNode);
41 +};
perf/tests/setState-callback.js new
+33
@@ -0,0 +1,33 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'From setState to callback';
6 +
7 +exports.defer = true;
8 +
9 +exports.setup = function(){
10 + /*global*/_rootNode = document.createElement('div');
11 + document.body.appendChild(_rootNode);
12 + /*global*/setState = null;
13 +
14 + var AwesomeComponent = React.createClass({
15 + getInitialState: function(){
16 + return { random:null };
17 + },
18 + render: function(){
19 + if (!setState) setState = this.setState.bind(this);
20 + return React.DOM.div(null, this.state.random);
21 + }
22 + });
23 +
24 + React.renderComponent(AwesomeComponent(null), _rootNode);
25 +};
26 +exports.fn = function(deferred){
27 + setState({random: Date.now() + Math.random()}, function(){
28 + deferred.resolve();
29 + });
30 +};
31 +exports.teardown = function(){
32 + React.unmountComponentAtNode(_rootNode);
33 +};
perf/tests/shouldComponentUpdate.js new
+25
@@ -0,0 +1,25 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'shouldComponentUpdate';
6 +
7 +exports.setup = function(){
8 + var AwesomeComponent = React.createClass({
9 + shouldComponentUpdate: function(){
10 + return false;
11 + },
12 + render: function(){
13 + return React.DOM.div({});
14 + }
15 + });
16 +
17 + var _rootNode = document.createElement('div');
18 + document.body.appendChild(_rootNode);
19 +};
20 +exports.fn = function(){
21 + React.renderComponent(AwesomeComponent(null), _rootNode);
22 +};
23 +exports.teardown = function(){
24 + React.unmountComponentAtNode(_rootNode);
25 +};
perf/tests/todolist-add.js new
+27
@@ -0,0 +1,27 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'todolist from addItem to callback';
6 +
7 +exports.defer = true;
8 +
9 +exports.setup = function(){
10 + /*global*/_rootNode = document.createElement('div');
11 + document.body.appendChild(_rootNode);
12 + /*global*/_app = todolist.App({ fakeDataCount: 333 });
13 + React.renderComponent(_app, _rootNode);
14 +};
15 +exports.fn = function(deferred){
16 + var liCount = document.getElementsByTagName('li').length;
17 + _app.addItem(Math.random(), function(){
18 + if (document.getElementsByTagName('li').length <= liCount) throw Error('expected a list item to be added to the dom');
19 + deferred.resolve();
20 + });
21 +};
22 +exports.teardown = function(){
23 + React.unmountComponentAtNode(_rootNode);
24 + _rootNode.parentNode.removeChild(_rootNode);
25 + _rootNode = null;
26 + _app = null;
27 +};
perf/tests/todolist-do-stuff.js new
+54
@@ -0,0 +1,54 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +/*global*/_timesToRun = 2;
6 +
7 +exports.name = 'todolist add, complete, remove (x' + _timesToRun + ')';
8 +
9 +exports.defer = true;
10 +
11 +exports.setup = function(){
12 + /*global*/_rootNode = document.createElement('div');
13 + document.body.appendChild(_rootNode);
14 + /*global*/_app = todolist.App({ fakeDataCount: 333 });
15 + React.renderComponent(_app, _rootNode);
16 +};
17 +
18 +exports.fn = function(deferred){
19 + var originalLiCount = document.getElementsByTagName('li').length;
20 +
21 + var todos = [];
22 + var times = _timesToRun;
23 + while (times-- >= 0){
24 + todos.push(_app.addItem(times+1));
25 + }
26 +
27 + todos.forEach(function(todo){
28 + _app.setItemCompleted(todo.id);
29 + });
30 +
31 + todos.forEach(function(todo){
32 + _app.deleteItemById(todo.id);
33 + });
34 +
35 + todos = null;
36 +
37 + _app.addItem(Math.random(), function(todo){
38 + if (document.getElementsByTagName('li').length <= originalLiCount)
39 + throw Error('expected a list item to be added to the dom');
40 +
41 + _app.deleteItemById(todo.id, function(){
42 + if (document.getElementsByTagName('li').length != originalLiCount)
43 + throw Error('expected everything to be done by now');
44 + deferred.resolve();
45 + });
46 + });
47 +};
48 +
49 +exports.teardown = function(){
50 + React.unmountComponentAtNode(_rootNode);
51 + _rootNode.parentNode.removeChild(_rootNode);
52 + _rootNode = null;
53 + _app = null;
54 +};
perf/tests/todolist-edit.js new
+32
@@ -0,0 +1,32 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'todolist setItemCompleted';
6 +
7 +exports.defer = true;
8 +
9 +exports.setup = function(){
10 + /*global*/_rootNode = document.createElement('div');
11 + document.body.appendChild(_rootNode);
12 + /*global*/_app = todolist.App({ fakeDataCount: 333 });
13 + React.renderComponent(_app, _rootNode);
14 + /*global*/_todo1 = _app.addItem("Howdy 1!");
15 + /*global*/_todo2 = _app.addItem("Howdy 2!");
16 + /*global*/_todo3 = _app.addItem("Howdy 3!");
17 +};
18 +
19 +exports.fn = function(deferred){
20 + _app.setItemCompleted(_todo1.id, !_todo1.completed);
21 + _app.setItemCompleted(_todo2.id, !_todo2.completed);
22 + _app.setItemCompleted(_todo3.id, !_todo3.completed, function(){
23 + deferred.resolve();
24 + });
25 +};
26 +
27 +exports.teardown = function(){
28 + React.unmountComponentAtNode(_rootNode);
29 + _rootNode.parentNode.removeChild(_rootNode);
30 + _rootNode = null;
31 + _app = null;
32 +};
perf/tests/todolist-mount.js new
+23
@@ -0,0 +1,23 @@
1 +if (typeof exports == 'undefined') exports = {};
2 +
3 +/*http://benchmarkjs.com/docs#options*/
4 +
5 +exports.name = 'todolist from renderComponent to renderComponent callback (333 rows)';
6 +
7 +exports.defer = true;
8 +
9 +exports.setup = function(){
10 + if (typeof _rootNode != 'undefined') throw Error("should teardown before running setup again");
11 + /*global*/_rootNode = document.createElement('div');
12 + document.body.appendChild(_rootNode);
13 +};
14 +
15 +exports.fn = function(deferred){
16 + React.renderComponent(todolist.App({ fakeDataCount: 333 }), _rootNode, function(){ deferred.resolve(); });
17 +};
18 +
19 +exports.teardown = function(){
20 + React.unmountComponentAtNode(_rootNode);
21 + _rootNode.parentNode.removeChild(_rootNode);
22 + _rootNode = undefined;
23 +};
test/index.html
+1
@@ -11,6 +11,7 @@
11 '../node_modules/jasmine-tapreporter/src/tapreporter.js',
12 '../vendor/jasmine-jsreporter/jasmine-jsreporter.js',
13
14 + 'lib/postDataToURL.browser.js',
15 'lib/reportTestResults.browser.js',
16
17 '../build/react.js',
test/lib/postDataToURL.browser.js new
+34
@@ -0,0 +1,34 @@
1 +function createXMLHttpRequest(){
2 + try{return new XMLHttpRequest();}
3 + catch(e){}
4 + try {return new ActiveXObject("Msxml2.XMLHTTP");}
5 + catch (e) {}
6 + try {return new ActiveXObject("Microsoft.XMLHTTP");}
7 + catch (e) {}
8 +}
9 +
10 +function getURLSync(url){
11 + var request = createXMLHttpRequest();
12 + request.open('GET', url, /*asynchronous?*/false);
13 + return request.responseText;
14 +}
15 +
16 +function postDataToURL(data, url, callback) {
17 + if (!callback) callback = postDataToURL.defaultCallback;
18 + var request = createXMLHttpRequest();
19 + if (!request) return callback(Error('XMLHttpRequest is unsupported'));
20 + postDataToURL.running = (postDataToURL.running||0) + 1;
21 + request.onreadystatechange = function(){
22 + if (request.readyState != 4) return;
23 + request.onreadystatechange = null;
24 + postDataToURL.running = (postDataToURL.running||0) - 1;
25 + callback(request.status == 200 ? null : request.status, request.responseText);
26 + };
27 + request.open('POST', url);
28 + request.setRequestHeader('Content-Type', 'application/json');
29 + request.send(JSON.stringify(data));
30 +}
31 +
32 +postDataToURL.defaultCallback = function(error){
33 + // console.log('postDataToURL.defaultCallback', arguments)
34 +}
test/lib/reportTestResults.browser.js
-27
@@ -48,30 +48,3 @@ console._flush = function(){
48 };
49
50 }(window.jasmine.getEnv()));
51 -
52 -function createXMLHttpRequest(){
53 - try{return new XMLHttpRequest();}
54 - catch(e){}
55 - try {return new ActiveXObject("Msxml2.XMLHTTP");}
56 - catch (e) {}
57 - try {return new ActiveXObject("Microsoft.XMLHTTP");}
58 - catch (e) {}
59 -}
60 -function postDataToURL(data, url, callback) {
61 - if (!callback) callback = postDataToURL.defaultCallback;
62 - var request = createXMLHttpRequest();
63 - if (!request) return callback(Error('XMLHttpRequest is unsupported'));
64 - postDataToURL.running = (postDataToURL.running||0) + 1;
65 - request.onreadystatechange = function(){
66 - if (request.readyState != 4) return;
67 - request.onreadystatechange = null;
68 - postDataToURL.running = (postDataToURL.running||0) - 1;
69 - callback(request.status == 200 ? null : request.status, request.responseText);
70 - };
71 - request.open('POST', url);
72 - request.setRequestHeader('Content-Type', 'application/json');
73 - request.send(JSON.stringify(data));
74 -}
75 -postDataToURL.defaultCallback = function(error){
76 - // console.log('postDataToURL.defaultCallback', arguments)
77 -}