Skip to content

Commit e128a63

Browse files
committed
Add automatic memory leak detection tests
1 parent 1a66708 commit e128a63

File tree

5 files changed

+163
-65
lines changed

5 files changed

+163
-65
lines changed

tests/allTests.js

Lines changed: 4 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,14 @@
11
'use strict';
22

33
var testSuite = require('./test.js');
4-
var fs = require('fs');
54
var argv = require('optimist').default('laxMode', false).default('browser', 'chrome').argv;
5+
var frameworkPathLookup = require('./framework-path-lookup');
66
var rootUrl = 'http://localhost:8000/';
7-
var frameworkNamePattern = /^[a-z-_\d]+$/;
87

9-
var excludedFrameworks = [
10-
// this implementation deviates from the specification to such an extent that they are
11-
// not worth testing via a generic mechanism
12-
'gwt',
13-
// these implementations cannot be run offline, because they are hosted
14-
'firebase-angular', 'meteor', 'socketstream',
15-
// YUI is a special case here, it is not hosted, but fetches JS files dynamically
16-
'yui',
17-
// these frameworks take a long time to start-up, and there is no easy way to determine when they are ready
18-
'cujo',
19-
// sammyjs fails intermittently, it would appear that its state is sometimes updated asynchronously?
20-
'sammyjs',
21-
// elm-html batches UI updates with requestAnimationFrame which the tests
22-
// don't wait for
23-
'elm',
24-
// these are examples that have been removed or are empty folders
25-
'emberjs_require', 'dermis'
26-
];
8+
var list = frameworkPathLookup(argv.framework);
279

28-
// collect together the framework names from each of the subfolders
29-
var list = fs.readdirSync('../examples/')
30-
.map(function (folderName) {
31-
return { name: folderName, path: 'examples/' + folderName };
32-
});
33-
34-
// apps that are not hosted at the root of their folder need to be handled explicitly
35-
var exceptions = [
36-
{ name: 'chaplin-brunch', path: 'examples/chaplin-brunch/public' },
37-
{ name: 'angular-dart', path: 'examples/angular-dart/web' },
38-
{ name: 'duel', path: 'examples/duel/www' },
39-
{ name: 'vanilladart', path: 'examples/vanilladart/build/web' },
40-
{ name: 'canjs_require', path: 'examples/canjs_require/' },
41-
{ name: 'troopjs', path: 'examples/troopjs_require/' },
42-
{ name: 'thorax_lumbar', path: 'examples/thorax_lumbar/public' }
43-
];
44-
list = list.map(function (framework) {
45-
var exception = exceptions.filter(function (exFramework) {
46-
return exFramework.name === framework.name;
47-
});
48-
return exception.length > 0 ? exception[0] : framework;
49-
});
50-
51-
// filter out any folders that are not frameworks (.e.g hidden files)
52-
list = list.filter(function (framework) {
53-
return frameworkNamePattern.test(framework.name);
54-
});
55-
56-
// filter out un-supported implementations
57-
list = list.filter(function (framework) {
58-
return excludedFrameworks.indexOf(framework.name) === -1;
59-
});
60-
61-
// if a specific framework has been named, just run this one
62-
if (argv.framework) {
63-
list = list.filter(function (framework) {
64-
return [].concat(argv.framework).some(function (f) {
65-
return f === framework.name;
66-
});
67-
});
68-
69-
if (list.length === 0) {
70-
console.log('You have either requested an unknown or an un-supported framework');
71-
}
10+
if (list.length === 0) {
11+
console.log('You have either requested an unknown or an un-supported framework');
7212
}
7313

7414
// run the tests for each framework

tests/framework-path-lookup.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
var fs = require('fs');
2+
var frameworkNamePattern = /^[a-z-_\d]+$/;
3+
4+
var excludedFrameworks = [
5+
// this implementation deviates from the specification to such an extent that they are
6+
// not worth testing via a generic mechanism
7+
'gwt',
8+
// these implementations cannot be run offline, because they are hosted
9+
'firebase-angular', 'meteor', 'socketstream',
10+
// YUI is a special case here, it is not hosted, but fetches JS files dynamically
11+
'yui',
12+
// these frameworks take a long time to start-up, and there is no easy way to determine when they are ready
13+
'cujo',
14+
// sammyjs fails intermittently, it would appear that its state is sometimes updated asynchronously?
15+
'sammyjs',
16+
// elm-html batches UI updates with requestAnimationFrame which the tests
17+
// don't wait for
18+
'elm',
19+
// these are examples that have been removed or are empty folders
20+
'emberjs_require', 'dermis'
21+
];
22+
23+
module.exports = function (names) {
24+
// collect together the framework names from each of the subfolders
25+
var list = fs.readdirSync('../examples/')
26+
.map(function (folderName) {
27+
return { name: folderName, path: 'examples/' + folderName };
28+
});
29+
30+
// apps that are not hosted at the root of their folder need to be handled explicitly
31+
var exceptions = [
32+
{ name: 'chaplin-brunch', path: 'examples/chaplin-brunch/public' },
33+
{ name: 'angular-dart', path: 'examples/angular-dart/web' },
34+
{ name: 'duel', path: 'examples/duel/www' },
35+
{ name: 'vanilladart', path: 'examples/vanilladart/build/web' },
36+
{ name: 'canjs_require', path: 'examples/canjs_require/' },
37+
{ name: 'troopjs', path: 'examples/troopjs_require/' },
38+
{ name: 'thorax_lumbar', path: 'examples/thorax_lumbar/public' }
39+
];
40+
list = list.map(function (framework) {
41+
var exception = exceptions.filter(function (exFramework) {
42+
return exFramework.name === framework.name;
43+
});
44+
return exception.length > 0 ? exception[0] : framework;
45+
});
46+
47+
// filter out any folders that are not frameworks (.e.g hidden files)
48+
list = list.filter(function (framework) {
49+
return frameworkNamePattern.test(framework.name);
50+
});
51+
52+
// filter out un-supported implementations
53+
list = list.filter(function (framework) {
54+
return excludedFrameworks.indexOf(framework.name) === -1;
55+
});
56+
57+
return list.filter(function (framework) {
58+
return [].concat(names).some(function (f) {
59+
return f === framework.name;
60+
});
61+
});
62+
}

tests/memory.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
var drool = require('drool');
2+
var frameworkPathLookup = require('./framework-path-lookup');
3+
var argv = require('optimist').default('laxMode', false).default('browser', 'chrome').argv;
4+
var driverConfig = {
5+
chromeOptions: 'no-sandbox'
6+
};
7+
8+
if (typeof process.env.CHROME_PATH !== 'undefined') {
9+
driverConfig.chromeBinaryPath = process.env.CHROME_PATH;
10+
}
11+
12+
var driver = drool.start(driverConfig);
13+
var list = frameworkPathLookup(argv.framework);
14+
15+
function idApp() {
16+
return driver.findElement(drool.webdriver.By.css('#todoapp'))
17+
.then(function () { return true; })
18+
.thenCatch(function () { return false; });
19+
}
20+
21+
function newTodoSelector(name) {
22+
return idApp().then(function (isId) {
23+
if (isId) {
24+
return '#new-todo';
25+
}
26+
27+
return '.new-todo';
28+
});
29+
}
30+
31+
function listSelector(name) {
32+
return idApp().then(function (isId) {
33+
if (isId) {
34+
return '#todo-list li';
35+
}
36+
37+
return '.todo-list li';
38+
});
39+
}
40+
41+
list.forEach(function (framework) {
42+
drool.flow({
43+
repeatCount: 5,
44+
setup: function () {
45+
driver.get('http://localhost:8000/' + framework.path + '/index.html');
46+
},
47+
action: function (name) {
48+
driver.wait(function () {
49+
return driver.findElement(drool.webdriver.By.css(newTodoSelector(name)))
50+
.sendKeys('find magical goats', drool.webdriver.Key.ENTER)
51+
.thenCatch(function () {
52+
return false;
53+
})
54+
.then(function () {
55+
return driver.findElement(drool.webdriver.By.css(listSelector(name))).isDisplayed()
56+
.then(function () {
57+
return true;
58+
})
59+
});
60+
}, 10000);
61+
62+
driver.wait(function () {
63+
return driver.findElement(drool.webdriver.By.css(listSelector(name))).click()
64+
.thenCatch(function () {
65+
return false;
66+
})
67+
.then(function () {
68+
return true;
69+
});
70+
});
71+
72+
driver.findElement(drool.webdriver.By.css('.destroy')).click();
73+
}.bind(null, framework.name),
74+
assert: function (after, initial) {
75+
var nodeIncrease = (after.nodes - initial.nodes);
76+
var listenerIncrease = (after.jsEventListeners - initial.jsEventListeners);
77+
console.log(this + ', ' + nodeIncrease + ', ' +
78+
(after.jsHeapSizeUsed - initial.jsHeapSizeUsed) + ', ' + listenerIncrease);
79+
80+
//https://code.google.com/p/chromium/issues/detail?id=516153
81+
if (nodeIncrease > 5) {
82+
throw new Error('Node Count leak detected!');
83+
}
84+
85+
if (listenerIncrease > 0) {
86+
throw new Error('Event Listener leak detected!');
87+
}
88+
89+
}.bind(framework.name)
90+
}, driver)
91+
});
92+
93+
driver.quit();
94+

tests/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"mocha": "*",
99
"mocha-known-issues-reporter": "git+https://github.com/ColinEberhardt/mocha-known-issues-reporter.git#v0.0.0",
1010
"optimist": "^0.6.1",
11-
"selenium-webdriver": "^2.46.1"
11+
"selenium-webdriver": "^2.46.1",
12+
"drool": "0.2.2"
1213
},
1314
"scripts": {
1415
"serve": "http-server -p 8000 ..",

tests/run.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
args="$@"
44

55
npm i && \
6+
eval "node memory.js $args" && \
67
eval "npm test -- $args"

0 commit comments

Comments
 (0)