Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'http-memleak' into v0.6

  • Loading branch information...
commit d1effbb338fe5916e5a25fc3d3587016d0725761 2 parents ebd0f98 + 2fc528c
@isaacs isaacs authored
View
1  .gitignore
@@ -40,3 +40,4 @@ ipch/
email.md
blog.html
deps/v8-*
+node_modules
View
13 Makefile
@@ -32,7 +32,7 @@ install:
uninstall:
@$(WAF) uninstall
-test: all
+test: all node_modules/weak
$(PYTHON) tools/test.py --mode=release simple message
test-http1: all
@@ -41,7 +41,15 @@ test-http1: all
test-valgrind: all
$(PYTHON) tools/test.py --mode=release --valgrind simple message
-test-all: all
+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
@@ -153,6 +161,7 @@ clean:
$(WAF) clean
-find tools -name "*.pyc" | xargs rm -f
-rm -rf blog.html email.md
+ -rm -rf node_modules
distclean: docclean
-find tools -name "*.pyc" | xargs rm -f
View
272 lib/http.js
@@ -35,113 +35,120 @@ if (process.env.NODE_DEBUG && /http/.test(process.env.NODE_DEBUG)) {
debug = function() { };
}
+function parserOnHeaders(headers, url) {
+ this._headers = this._headers.concat(headers);
+ this._url += url;
+}
-var parsers = new FreeList('parsers', 1000, function() {
- var parser = new HTTPParser(HTTPParser.REQUEST);
+// info.headers and info.url are set only if .onHeaders()
+// has not been called for this request.
+//
+// info.url is not set for response parsers but that's not
+// applicable here since all our parsers are request parsers.
+function parserOnHeadersComplete(info) {
+ var parser = this;
+ var headers = info.headers;
+ var url = info.url;
+
+ if (!headers) {
+ headers = parser._headers;
+ parser._headers = [];
+ }
- parser._headers = [];
- parser._url = '';
+ if (!url) {
+ url = parser._url;
+ parser._url = '';
+ }
- // Only called in the slow case where slow means
- // that the request headers were either fragmented
- // across multiple TCP packets or too large to be
- // processed in a single run. This method is also
- // called to process trailing HTTP headers.
- parser.onHeaders = function(headers, url) {
- parser._headers = parser._headers.concat(headers);
- parser._url += url;
- };
+ parser.incoming = new IncomingMessage(parser.socket);
+ parser.incoming.httpVersionMajor = info.versionMajor;
+ parser.incoming.httpVersionMinor = info.versionMinor;
+ parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor;
+ parser.incoming.url = url;
- // info.headers and info.url are set only if .onHeaders()
- // has not been called for this request.
- //
- // info.url is not set for response parsers but that's not
- // applicable here since all our parsers are request parsers.
- parser.onHeadersComplete = function(info) {
- var headers = info.headers;
- var url = info.url;
-
- if (!headers) {
- headers = parser._headers;
- parser._headers = [];
- }
+ for (var i = 0, n = headers.length; i < n; i += 2) {
+ var k = headers[i];
+ var v = headers[i + 1];
+ parser.incoming._addHeaderLine(k.toLowerCase(), v);
+ }
- if (!url) {
- url = parser._url;
- parser._url = '';
- }
+ if (info.method) {
+ // server only
+ parser.incoming.method = info.method;
+ } else {
+ // client only
+ parser.incoming.statusCode = info.statusCode;
+ // CHECKME dead code? we're always a request parser
+ }
- parser.incoming = new IncomingMessage(parser.socket);
- parser.incoming.httpVersionMajor = info.versionMajor;
- parser.incoming.httpVersionMinor = info.versionMinor;
- parser.incoming.httpVersion = info.versionMajor + '.' + info.versionMinor;
- parser.incoming.url = url;
+ parser.incoming.upgrade = info.upgrade;
- for (var i = 0, n = headers.length; i < n; i += 2) {
- var k = headers[i];
- var v = headers[i + 1];
- parser.incoming._addHeaderLine(k.toLowerCase(), v);
- }
+ var isHeadResponse = false;
- if (info.method) {
- // server only
- parser.incoming.method = info.method;
- } else {
- // client only
- parser.incoming.statusCode = info.statusCode;
- // CHECKME dead code? we're always a request parser
- }
+ if (!info.upgrade) {
+ // For upgraded connections, we'll emit this after parser.execute
+ // so that we can capture the first part of the new protocol
+ isHeadResponse = parser.onIncoming(parser.incoming, info.shouldKeepAlive);
+ }
- parser.incoming.upgrade = info.upgrade;
+ return isHeadResponse;
+}
- var isHeadResponse = false;
+function parserOnBody(b, start, len) {
+ var parser = this;
+ // TODO body encoding?
+ var slice = b.slice(start, start + len);
+ if (parser.incoming._decoder) {
+ var string = parser.incoming._decoder.write(slice);
+ if (string.length) parser.incoming.emit('data', string);
+ } else {
+ parser.incoming.emit('data', slice);
+ }
+}
+function parserOnMessageComplete() {
+ var parser = this;
+ parser.incoming.complete = true;
- if (!info.upgrade) {
- // For upgraded connections, we'll emit this after parser.execute
- // so that we can capture the first part of the new protocol
- isHeadResponse = parser.onIncoming(parser.incoming, info.shouldKeepAlive);
+ // Emit any trailing headers.
+ var headers = parser._headers;
+ if (headers) {
+ for (var i = 0, n = headers.length; i < n; i += 2) {
+ var k = headers[i];
+ var v = headers[i + 1];
+ parser.incoming._addHeaderLine(k.toLowerCase(), v);
}
+ parser._headers = [];
+ parser._url = '';
+ }
- return isHeadResponse;
- };
+ if (!parser.incoming.upgrade) {
+ // For upgraded connections, also emit this after parser.execute
+ parser.incoming.readable = false;
+ parser.incoming.emit('end');
+ }
- parser.onBody = function(b, start, len) {
- // TODO body encoding?
- var slice = b.slice(start, start + len);
- if (parser.incoming._decoder) {
- var string = parser.incoming._decoder.write(slice);
- if (string.length) parser.incoming.emit('data', string);
- } else {
- parser.incoming.emit('data', slice);
- }
- };
+ if (parser.socket.readable) {
+ // force to read the next incoming message
+ parser.socket.resume();
+ }
+}
- parser.onMessageComplete = function() {
- parser.incoming.complete = true;
- // Emit any trailing headers.
- var headers = parser._headers;
- if (headers) {
- for (var i = 0, n = headers.length; i < n; i += 2) {
- var k = headers[i];
- var v = headers[i + 1];
- parser.incoming._addHeaderLine(k.toLowerCase(), v);
- }
- parser._headers = [];
- parser._url = '';
- }
+var parsers = new FreeList('parsers', 1000, function() {
+ var parser = new HTTPParser(HTTPParser.REQUEST);
- if (!parser.incoming.upgrade) {
- // For upgraded connections, also emit this after parser.execute
- parser.incoming.readable = false;
- parser.incoming.emit('end');
- }
+ parser._headers = [];
+ parser._url = '';
- if (parser.socket.readable) {
- // force to read the next incoming message
- parser.socket.resume();
- }
- };
+ // Only called in the slow case where slow means
+ // that the request headers were either fragmented
+ // across multiple TCP packets or too large to be
+ // processed in a single run. This method is also
+ // called to process trailing HTTP headers.
+ parser.onHeaders = parserOnHeaders;
+ parser.onHeadersComplete = parserOnHeadersComplete;
+ parser.onBody = parserOnBody;
+ parser.onMessageComplete = parserOnMessageComplete;
return parser;
});
@@ -1014,7 +1021,7 @@ function ClientRequest(options, cb) {
var method = self.method = (options.method || 'GET').toUpperCase();
self.path = options.path || '/';
if (cb) {
- self.on('response', cb);
+ self.once('response', cb);
}
if (!Array.isArray(options.headers)) {
@@ -1079,6 +1086,7 @@ function ClientRequest(options, cb) {
self._deferToConnect(null, null, function() {
self._flush();
+ self = null;
});
}
@@ -1109,6 +1117,31 @@ function createHangUpError() {
return error;
}
+// Free the parser and also break any links that it
+// might have to any other things.
+// TODO: All parser data should be attached to a
+// single object, so that it can be easily cleaned
+// up by doing `parser.data = {}`, which should
+// be done in FreeList.free. `parsers.free(parser)`
+// should be all that is needed.
+function freeParser(parser, req) {
+ if (parser) {
+ parser._headers = [];
+ parser.onIncoming = null;
+ if (parser.socket) {
+ parser.socket.onend = null;
+ parser.socket.ondata = null;
+ }
+ parser.socket = null;
+ parser.incoming = null;
+ parsers.free(parser);
+ parser = null;
+ }
+ if (req) {
+ req.parser = null;
+ }
+};
+
ClientRequest.prototype.onSocket = function(socket) {
var req = this;
@@ -1125,13 +1158,6 @@ ClientRequest.prototype.onSocket = function(socket) {
// Setup "drain" propogation.
httpSocketSetup(socket);
- var freeParser = function() {
- if (parser) {
- parsers.free(parser);
- parser = null;
- }
- };
-
var errorListener = function(err) {
debug('HTTP SOCKET ERROR: ' + err.message + '\n' + err.stack);
req.emit('error', err);
@@ -1140,7 +1166,7 @@ ClientRequest.prototype.onSocket = function(socket) {
req._hadError = true;
if (parser) {
parser.finish();
- freeParser();
+ freeParser(parser, req);
}
socket.destroy();
}
@@ -1150,7 +1176,7 @@ ClientRequest.prototype.onSocket = function(socket) {
var ret = parser.execute(d, start, end - start);
if (ret instanceof Error) {
debug('parse error');
- freeParser();
+ freeParser(parser, req);
socket.destroy(ret);
} else if (parser.incoming && parser.incoming.upgrade) {
var bytesParsed = ret;
@@ -1171,13 +1197,13 @@ ClientRequest.prototype.onSocket = function(socket) {
// Got upgrade header, but have no handler.
socket.destroy();
}
- freeParser();
+ freeParser(parser, req);
} else if (parser.incoming && parser.incoming.complete &&
// When the status code is 100 (Continue), the server will
// send a final response after this client sends a request
// body. So, we must not free the parser.
parser.incoming.statusCode !== 100) {
- freeParser();
+ freeParser(parser, req);
}
};
@@ -1190,7 +1216,7 @@ ClientRequest.prototype.onSocket = function(socket) {
}
if (parser) {
parser.finish();
- freeParser();
+ freeParser(parser, req);
}
socket.destroy();
};
@@ -1209,6 +1235,13 @@ ClientRequest.prototype.onSocket = function(socket) {
// fire on the request.
req.emit('error', createHangUpError());
}
+
+ // Nothing more to be done with this req, since the socket
+ // is closed, and we've emitted the appropriate abort/end/close/error
+ // events. Disavow all knowledge, and break the references to
+ // the variables trapped by closures and on the socket object.
+ req = null;
+ socket._httpMessage = null;
}
socket.on('close', closeListener);
@@ -1274,6 +1307,7 @@ ClientRequest.prototype.onSocket = function(socket) {
});
};
+
ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {
// This function is for calls that need to happen once the socket is
// connected and writable. It's an important promisy thing for all the socket
@@ -1302,9 +1336,33 @@ ClientRequest.prototype._deferToConnect = function(method, arguments_, cb) {
onSocket();
}
};
-ClientRequest.prototype.setTimeout = function() {
- this._deferToConnect('setTimeout', arguments);
+
+ClientRequest.prototype.setTimeout = function(msecs, callback) {
+ if (callback) this.once('timeout', callback);
+
+ var self = this;
+ function emitTimeout() {
+ self.emit('timeout');
+ self.destroy(new Error('timeout'));
+ }
+
+ if (this.socket && this.socket.writable) {
+ this.socket.setTimeout(msecs, emitTimeout);
+ return;
+ }
+
+ if (this.socket) {
+ this.socket.once('connect', function() {
+ this.setTimeout(msecs, emitTimeout);
+ });
+ return;
+ }
+
+ this.once('socket', function(sock) {
+ this.setTimeout(msecs, emitTimeout);
+ });
};
+
ClientRequest.prototype.setNoDelay = function() {
this._deferToConnect('setNoDelay', arguments);
};
@@ -1393,7 +1451,7 @@ function connectionListener(socket) {
httpSocketSetup(socket);
socket.setTimeout(2 * 60 * 1000); // 2 minute timeout
- socket.addListener('timeout', function() {
+ socket.once('timeout', function() {
socket.destroy();
});
@@ -1453,8 +1511,8 @@ function connectionListener(socket) {
socket.addListener('close', function() {
debug('server socket close');
- // unref the parser for easy gc
- parsers.free(parser);
+ // mark this parser as reusable
+ freeParser(parser);
abortIncoming();
});
View
64 test/gc/test-http-client-connaborted.js
@@ -0,0 +1,64 @@
+// 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 = 500,
+ 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 ++;
+}
+
+var timer;
+function statusLater() {
+ gc();
+ if (timer) clearTimeout(timer);
+ timer = 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);
+ }
+}
View
69 test/gc/test-http-client-onerror.js
@@ -0,0 +1,69 @@
+// 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 = 500,
+ 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 ++;
+}
+
+var timer;
+function statusLater() {
+ gc();
+ if (timer) clearTimeout(timer);
+ timer = 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);
+ }
+}
+
View
72 test/gc/test-http-client-timeout.js
@@ -0,0 +1,72 @@
+// 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 = 500,
+ 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 ++;
+}
+
+var timer;
+function statusLater() {
+ gc();
+ if (timer) clearTimeout(timer);
+ timer = 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);
+ }
+}
+
View
67 test/gc/test-http-client.js
@@ -0,0 +1,67 @@
+// 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 = 500,
+ 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 ++;
+}
+
+var timer;
+function statusLater() {
+ gc();
+ if (timer) clearTimeout(timer);
+ timer = 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);
+ }
+}
+
View
64 test/gc/test-net-timeout.js
@@ -0,0 +1,64 @@
+// 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 = 500,
+ 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 ++;
+}
+
+var timer;
+function statusLater() {
+ gc();
+ if (timer) clearTimeout(timer);
+ timer = 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);
+ }
+}
+
View
133 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)
View
2  tools/test.py
@@ -1275,7 +1275,7 @@ def ExpandCommand(args):
return ExpandCommand
-BUILT_IN_TESTS = ['simple', 'pummel', 'message', 'internet']
+BUILT_IN_TESTS = ['simple', 'pummel', 'message', 'internet', 'gc']
def GetSuites(test_root):
Please sign in to comment.
Something went wrong with that request. Please try again.