From: isaacs Date: Thu, 3 May 2012 17:16:25 +0000 (-0700) Subject: Tests for memory leaks X-Git-Tag: v0.7.9~84^2~5 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=e63c7821d5fc4e2f92c0606ab16b7846893ac3d1;p=platform%2Fupstream%2Fnodejs.git Tests for memory leaks Conflicts: Makefile --- diff --git a/.gitignore b/.gitignore index 3f22d71..e44636d 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ ipch/ email.md blog.html deps/v8-* +node_modules diff --git a/Makefile b/Makefile index 94b7eb9..04688e4 100644 --- a/Makefile +++ b/Makefile @@ -42,14 +42,16 @@ uninstall: clean: -rm -rf out/Makefile node node_g out/$(BUILDTYPE)/node blog.html email.md -find out/ -name '*.o' -o -name '*.a' | xargs rm -rf + -rm -rf node_modules distclean: -rm -rf out -rm -f config.gypi -rm -f config.mk -rm -rf node node_g blog.html email.md + -rm -rf node_modules -test: all +test: all node_modules/weak $(PYTHON) tools/test.py --mode=release simple message PYTHONPATH=tools/closure_linter/ $(PYTHON) tools/closure_linter/closure_linter/gjslint.py --unix_mode --strict --nojsdoc -r lib/ -r src/ --exclude_files lib/punycode.js @@ -59,9 +61,17 @@ test-http1: all test-valgrind: all $(PYTHON) tools/test.py --mode=release --valgrind simple message -test-all: all - python tools/test.py --mode=debug,release - $(MAKE) test-npm +node_modules/weak: + @if [ ! -f node ]; then make all; fi + @if [ ! -d node_modules ]; then mkdir -p node_modules; fi + ./node deps/npm/bin/npm-cli.js install weak --prefix="$(shell pwd)" + +test-gc: all node_modules/weak + $(PYTHON) tools/test.py --mode=release gc + +test-all: all node_modules/weak + $(PYTHON) tools/test.py --mode=debug,release + make test-npm test-all-http1: all $(PYTHON) tools/test.py --mode=debug,release --use-http1 diff --git a/test/gc/test-http-client-connaborted.js b/test/gc/test-http-client-connaborted.js new file mode 100644 index 0000000..84d7ca5 --- /dev/null +++ b/test/gc/test-http-client-connaborted.js @@ -0,0 +1,61 @@ +// just like test/gc/http-client.js, +// but aborting every connection that comes in. + +function serverHandler(req, res) { + res.connection.destroy(); +} + +var http = require('http'), + weak = require('weak'), + done = 0, + count = 0, + countGC = 0, + todo = 18, + common = require('../common.js'), + assert = require('assert'), + PORT = common.PORT; + +console.log('We should do '+ todo +' requests'); + +var http = require('http'); +var server = http.createServer(serverHandler); +server.listen(PORT, getall); + +function getall() { + for (var i = 0; i < todo; i++) { + (function(){ + function cb(res) { + done+=1; + statusLater(); + } + + var req = http.get({ + hostname: 'localhost', + pathname: '/', + port: PORT + }, cb).on('error', cb); + + count++; + weak(req, afterGC); + })() + } +} + +function afterGC(){ + countGC ++; +} + +function statusLater() { + setTimeout(status, 1); +} + +function status() { + gc(); + console.log('Done: %d/%d', done, todo); + console.log('Collected: %d/%d', countGC, count); + if (done === todo) { + console.log('All should be collected now.'); + assert(count === countGC); + process.exit(0); + } +} diff --git a/test/gc/test-http-client-onerror.js b/test/gc/test-http-client-onerror.js new file mode 100644 index 0000000..5872489 --- /dev/null +++ b/test/gc/test-http-client-onerror.js @@ -0,0 +1,66 @@ +// just like test/gc/http-client.js, +// but with an on('error') handler that does nothing. + +function serverHandler(req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Hello World\n'); +} + +var http = require('http'), + weak = require('weak'), + done = 0, + count = 0, + countGC = 0, + todo = 18, + common = require('../common.js'), + assert = require('assert'), + PORT = common.PORT; + +console.log('We should do '+ todo +' requests'); + +var http = require('http'); +var server = http.createServer(serverHandler); +server.listen(PORT, getall); + +function getall() { + for (var i = 0; i < todo; i++) { + (function(){ + function cb(res) { + done+=1; + statusLater(); + } + function onerror(er) { + throw er; + } + + var req = http.get({ + hostname: 'localhost', + pathname: '/', + port: PORT + }, cb).on('error', onerror); + + count++; + weak(req, afterGC); + })() + } +} + +function afterGC(){ + countGC ++; +} + +function statusLater() { + setTimeout(status, 1); +} + +function status() { + gc(); + console.log('Done: %d/%d', done, todo); + console.log('Collected: %d/%d', countGC, count); + if (done === todo) { + console.log('All should be collected now.'); + assert(count === countGC); + process.exit(0); + } +} + diff --git a/test/gc/test-http-client-timeout.js b/test/gc/test-http-client-timeout.js new file mode 100644 index 0000000..32302a4 --- /dev/null +++ b/test/gc/test-http-client-timeout.js @@ -0,0 +1,69 @@ +// just like test/gc/http-client.js, +// but with a timeout set + +function serverHandler(req, res) { + setTimeout(function () { + res.writeHead(200) + res.end('hello\n'); + }, 100); +} + +var http = require('http'), + weak = require('weak'), + done = 0, + count = 0, + countGC = 0, + todo = 18, + common = require('../common.js'), + assert = require('assert'), + PORT = common.PORT; + +console.log('We should do '+ todo +' requests'); + +var http = require('http'); +var server = http.createServer(serverHandler); +server.listen(PORT, getall); + +function getall() { + for (var i = 0; i < todo; i++) { + (function(){ + function cb() { + done+=1; + statusLater(); + } + + var req = http.get({ + hostname: 'localhost', + pathname: '/', + port: PORT + }, cb); + req.on('error', cb); + req.setTimeout(10, function(){ + console.log('timeout (expected)') + }); + + count++; + weak(req, afterGC); + })() + } +} + +function afterGC(){ + countGC ++; +} + +function statusLater() { + setTimeout(status, 1); +} + +function status() { + gc(); + console.log('Done: %d/%d', done, todo); + console.log('Collected: %d/%d', countGC, count); + if (done === todo) { + console.log('All should be collected now.'); + assert(count === countGC); + process.exit(0); + } +} + diff --git a/test/gc/test-http-client.js b/test/gc/test-http-client.js new file mode 100644 index 0000000..913ca7b --- /dev/null +++ b/test/gc/test-http-client.js @@ -0,0 +1,63 @@ +// just a simple http server and client. + +function serverHandler(req, res) { + res.writeHead(200, {'Content-Type': 'text/plain'}); + res.end('Hello World\n'); +} + +var http = require('http'), + weak = require('weak'), + done = 0, + count = 0, + countGC = 0, + todo = 5, + common = require('../common.js'), + assert = require('assert'), + PORT = common.PORT; + +console.log('We should do '+ todo +' requests'); + +var http = require('http'); +var server = http.createServer(serverHandler); +server.listen(PORT, getall); + + +function getall() { + for (var i = 0; i < todo; i++) { + (function(){ + function cb(res) { + console.error('in cb') + done+=1; + res.on('end', statusLater); + } + + var req = http.get({ + hostname: 'localhost', + pathname: '/', + port: PORT + }, cb) + + count++; + weak(req, afterGC); + })() + } +} + +function afterGC(){ + countGC ++; +} + +function statusLater() { + setTimeout(status, 1); +} + +function status() { + gc(); + console.log('Done: %d/%d', done, todo); + console.log('Collected: %d/%d', countGC, count); + if (done === todo) { + console.log('All should be collected now.'); + assert(count === countGC); + process.exit(0); + } +} diff --git a/test/gc/test-net-timeout.js b/test/gc/test-net-timeout.js new file mode 100644 index 0000000..789193e --- /dev/null +++ b/test/gc/test-net-timeout.js @@ -0,0 +1,61 @@ +// just like test/gc/http-client-timeout.js, +// but using a net server/client instead + +function serverHandler(sock) { + sock.setTimeout(120000); + setTimeout(function () { + sock.end('hello\n'); + }, 100); +} + +var net = require('net'), + weak = require('weak'), + done = 0, + count = 0, + countGC = 0, + todo = 18, + common = require('../common.js'), + assert = require('assert'), + PORT = common.PORT; + +console.log('We should do '+ todo +' requests'); + +var server = net.createServer(serverHandler); +server.listen(PORT, getall); + +function getall() { + for (var i = 0; i < todo; i++) { + (function(){ + var req = net.connect(PORT, '127.0.0.1'); + req.setTimeout(10, function() { + console.log('timeout (expected)') + req.destroy(); + done++; + statusLater(); + }); + + count++; + weak(req, afterGC); + })() + } +} + +function afterGC(){ + countGC ++; +} + +function statusLater() { + setTimeout(status, 1); +} + +function status() { + gc(); + console.log('Done: %d/%d', done, todo); + console.log('Collected: %d/%d', countGC, count); + if (done === todo) { + console.log('All should be collected now.'); + assert(count === countGC); + process.exit(0); + } +} + diff --git a/test/gc/testcfg.py b/test/gc/testcfg.py new file mode 100644 index 0000000..30f3bdb --- /dev/null +++ b/test/gc/testcfg.py @@ -0,0 +1,133 @@ +# Copyright 2008 the V8 project authors. All rights reserved. +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# * Neither the name of Google Inc. nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import test +import os +import shutil +from shutil import rmtree +from os import mkdir +from glob import glob +from os.path import join, dirname, exists +import re + + +FLAGS_PATTERN = re.compile(r"//\s+Flags:(.*)") +FILES_PATTERN = re.compile(r"//\s+Files:(.*)") + + +class GCTestCase(test.TestCase): + + def __init__(self, path, file, mode, context, config): + super(GCTestCase, self).__init__(context, path, mode) + self.file = file + self.config = config + self.mode = mode + self.tmpdir = join(dirname(self.config.root), 'tmp') + + def AfterRun(self, result): + # delete the whole tmp dir + try: + rmtree(self.tmpdir) + except: + pass + # make it again. + try: + mkdir(self.tmpdir) + except: + pass + + def BeforeRun(self): + # delete the whole tmp dir + try: + rmtree(self.tmpdir) + except: + pass + # make it again. + # intermittently fails on win32, so keep trying + while not os.path.exists(self.tmpdir): + try: + mkdir(self.tmpdir) + except: + pass + + def GetLabel(self): + return "%s %s" % (self.mode, self.GetName()) + + def GetName(self): + return self.path[-1] + + def GetCommand(self): + result = [self.config.context.GetVm(self.mode)] + source = open(self.file).read() + flags_match = FLAGS_PATTERN.search(source) + if flags_match: + result += flags_match.group(1).strip().split() + files_match = FILES_PATTERN.search(source); + additional_files = [] + if files_match: + additional_files += files_match.group(1).strip().split() + for a_file in additional_files: + result.append(join(dirname(self.config.root), '..', a_file)) + result += ["--expose-gc"] + result += [self.file] + return result + + def GetSource(self): + return open(self.file).read() + + +class GCTestConfiguration(test.TestConfiguration): + + def __init__(self, context, root): + super(GCTestConfiguration, self).__init__(context, root) + + def Ls(self, path): + def SelectTest(name): + return name.startswith('test-') and name.endswith('.js') + return [f[:-3] for f in os.listdir(path) if SelectTest(f)] + + def ListTests(self, current_path, path, mode): + all_tests = [current_path + [t] for t in self.Ls(join(self.root))] + result = [] + for test in all_tests: + if self.Contains(path, test): + file_path = join(self.root, reduce(join, test[1:], "") + ".js") + result.append(GCTestCase(test, file_path, mode, self.context, self)) + return result + + def GetBuildRequirements(self): + return ['sample', 'sample=shell'] + + def GetTestStatus(self, sections, defs): + status_file = join(self.root, 'gc.status') + if exists(status_file): + test.ReadConfigurationInto(status_file, sections, defs) + + + +def GetConfiguration(context, root): + return GCTestConfiguration(context, root) diff --git a/tools/test.py b/tools/test.py index eed3c65..5be025e 100755 --- a/tools/test.py +++ b/tools/test.py @@ -1275,7 +1275,7 @@ def GetSpecialCommandProcessor(value): return ExpandCommand -BUILT_IN_TESTS = ['simple', 'pummel', 'message', 'internet'] +BUILT_IN_TESTS = ['simple', 'pummel', 'message', 'internet', 'gc'] def GetSuites(test_root):