first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"env" : {
"node" : true
},
"rules" : {
"curly" : 0,
"no-lonely-if" : 1,
"no-mixed-requires" : 0,
"no-underscore-dangle" : 0,
"no-unused-vars" : [2, {"vars" : "all", "args" : "after-used"}],
"no-use-before-define" : [2, "nofunc"],
"quotes" : 0,
"semi" : [2, "always"],
"space-after-keywords" : 1,
"space-infix-ops" : 0,
"strict" : 0,
"max-len" : [1, 90, 2]
}
}
+14
View File
@@ -0,0 +1,14 @@
script:
- "npm test"
language: node_js
node_js:
- "0.10"
- "0.12"
- "4"
- "6"
- "8"
- "9"
sudo: false
+168
View File
@@ -0,0 +1,168 @@
### v3.1.0 (2014-07-28):
* Updated to use `async-listener@0.4.7` to pick up bug fixes.
### v3.0.0 (2013-12-14):
* Removed the notion of a "default" or "global" context per namespace. It only
existed to create a simpler interface for developing and testing the module,
and created the potential for nasty information disclosure bugs (see [issue
#14](https://github.com/othiym23/node-continuation-local-storage/issues/14)
for details). This is potentially a breaking change, if you're depending on
the global context, so semver says we have to bump the major version.
* Added this changelog.
### v2.6.2 (2013-12-07):
* `async-listener` and `emitter-listener` dependency refresh.
### v2.6.1 (2013-11-29):
* `emitter-listener` has been extracted from `shimmer` into a standalone
module for `namespace.bindEmitter()`.
### v2.6.0 (2013-11-27):
* When an error is thrown in a CLS-bound continuation chain, attach the active
context for the namespace to which the chain is bound. This is necessary
because CLS and asyncListeners actually do too good a job of cleaning up
after errors, and so they don't escape the continuation chain. New Relic
needs the context so it can get the transaction active when the error
happened for error tracing.
### v2.5.2 (2013-10-30):
* `async-listener` dependency refresh for better support of node 0.8.0 - 0.8.3.
### v2.5.1 (2013-10-27):
* `async-listener` dependency refresh.
### v2.5.0 (2013-10-27):
* Relax the requirement that CLS contexts be pushed and popped from a stack,
instead treating them as a set. This allows context interleaving (i.e.
using the lower-level `namespace.enter()` and `namespace.exit()` API without
any strict ordering dependencies). Everything works, but this still makes me
a little uneasy.
* EEs can now be bound to multiple namespaces, although this is likely to be
slow.
### v2.4.4 (2013-10-27):
* Even if you use an EE bound to a namespace outside a continuation chain, it
shouldn't explode.
### v2.4.3 (2013-10-16):
* `async-listener` dependency refresh.
### v2.4.2 (2013-10-13):
* More tweaks for `async-listener` error handlers (just a dependency refresh).
### v2.4.1 (2013-10-12):
* `async-listener` error listeners have gotten lots of tweaks. Update to newest
API.
* Only exit namespace context on error if a continuation chain is active.
### v2.4.0 (2013-10-11):
* `async-listener` now supports error listeners. Update to newest API.
* Namespace context should be exited on asynchronous errors.
### v2.3.4 (2013-10-03):
* When EEs are in the middle of emitting, make sure that calls to
`emitter.removeListener` are testing against non-monkeypatched versions of
the event handlers (necessary so certain Connect middleware functions, such
as `connect.limit`, run correctly).
### v2.3.3 (2013-10-02):
* Ensure handler rebinding gets called even in case of errors.
* Be consistent about making sure contexts are kept in a sane state when errors
are thrown in EEs.
### v2.3.2 (2013-10-02):
* Guard `on` / `addListener` remonkeypatching in `namespace.bindEmitter()` so
that `shimmer` is only called to rebind if the monkeypatched versions have
actually been replaced.
* Don't try to call emit if there are no listeners on a bound EE.
* Don't use `setImmediate` in tests, because it's not available in Node 0.8.x.
### v2.3.1 (2013-10-01):
* Update to newest version of `async-listener`.
* Fix typo.
### v2.3.0 (2013-09-30):
* EventEmitters can now be bound to CLS namespaces. Because EEs act as coupling
points between asynchronous domains, it's necessary for the EE binding to
capture the CLS context both when the listener is added, and when a matching
handler is firing because of a matching event being emitted.
### v2.2.1 (2013-09-30):
* More tweaks to conform with `asyncListener` API changes.
* Many more test cases to ensure `asyncListener` stuff is working with Node
0.8.x.
### v2.2.0 (2013-09-26):
* Square up with latest `async-listener` / node PR #6011 changes.
### v2.1.2 (2013-09-09):
* Document `namespace.createContext()`.
* Fix issue where a value was *always* being returned from `namespace.run()`,
even on error.
### v2.1.1 (2013-09-03):
* Clean up minor typo in docs.
### v2.1.0 (2013-09-03):
* Incorporate documentation from failed CLS PR.
* `namespace.bind()` now also always exits the domain, even on error.
* Namespaces can be destroyed.
* `cls.reset()` allows tests to nuke all existing namespaces (use with care
obviously).
### v2.0.0 (2013-09-01):
* Use `async-listener` polyfill instead of `cls-glue`.
* Incorporate tests from `cls-glue`.
### v1.1.1 (2013-09-01):
* Namespace exits context even on error.
### v1.1.0 (2013-07-30):
* Split createContext so it's part of the namespace API.
* Tweak error message to be more informative.
### v1.0.1 (2013-07-25):
* Correct Tim's email address.
### v1.0.0 (2013-07-25):
* Each application of CLS is allocated its own "namespace", which bind data to
continuation chains, either using `.run()` or `.bind()` to create a new
nested context. These nested contexts are prototype chains that point back to
a "default" / "global" context, with the default context for each namespace
being a prototype-free "data bag" created with `Object.create(null)`.
### v0.1.1 (2013-05-03):
* Document progress thus far.
### v0.1.0 (2013-05-03):
* First attempt: basic API, docs, and tests.
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2013-2016, Forrest L Norvell <ogd@aoaioxxysz.net>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. 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.
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.
+273
View File
@@ -0,0 +1,273 @@
[![NPM](https://nodei.co/npm/continuation-local-storage.png?downloads=true&stars=true)](https://nodei.co/npm/continuation-local-storage/)
# Continuation-Local Storage
Continuation-local storage works like thread-local storage in threaded
programming, but is based on chains of Node-style callbacks instead of threads.
The standard Node convention of functions calling functions is very similar to
something called ["continuation-passing style"][cps] in functional programming,
and the name comes from the way this module allows you to set and get values
that are scoped to the lifetime of these chains of function calls.
Suppose you're writing a module that fetches a user and adds it to a session
before calling a function passed in by a user to continue execution:
```javascript
// setup.js
var createNamespace = require('continuation-local-storage').createNamespace;
var session = createNamespace('my session');
var db = require('./lib/db.js');
function start(options, next) {
db.fetchUserById(options.id, function (error, user) {
if (error) return next(error);
session.set('user', user);
next();
});
}
```
Later on in the process of turning that user's data into an HTML page, you call
another function (maybe defined in another module entirely) that wants to fetch
the value you set earlier:
```javascript
// send_response.js
var getNamespace = require('continuation-local-storage').getNamespace;
var session = getNamespace('my session');
var render = require('./lib/render.js')
function finish(response) {
var user = session.get('user');
render({user: user}).pipe(response);
}
```
When you set values in continuation-local storage, those values are accessible
until all functions called from the original function synchronously or
asynchronously have finished executing. This includes callbacks passed to
`process.nextTick` and the [timer functions][] ([setImmediate][],
[setTimeout][], and [setInterval][]), as well as callbacks passed to
asynchronous functions that call native functions (such as those exported from
the `fs`, `dns`, `zlib` and `crypto` modules).
A simple rule of thumb is anywhere where you might have set a property on the
`request` or `response` objects in an HTTP handler, you can (and should) now
use continuation-local storage. This API is designed to allow you extend the
scope of a variable across a sequence of function calls, but with values
specific to each sequence of calls.
Values are grouped into namespaces, created with `createNamespace()`. Sets of
function calls are grouped together by calling them within the function passed
to `.run()` on the namespace object. Calls to `.run()` can be nested, and each
nested context this creates has its own copy of the set of values from the
parent context. When a function is making multiple asynchronous calls, this
allows each child call to get, set, and pass along its own context without
overwriting the parent's.
A simple, annotated example of how this nesting behaves:
```javascript
var createNamespace = require('continuation-local-storage').createNamespace;
var writer = createNamespace('writer');
writer.run(function () {
writer.set('value', 0);
requestHandler();
});
function requestHandler() {
writer.run(function(outer) {
// writer.get('value') returns 0
// outer.value is 0
writer.set('value', 1);
// writer.get('value') returns 1
// outer.value is 1
process.nextTick(function() {
// writer.get('value') returns 1
// outer.value is 1
writer.run(function(inner) {
// writer.get('value') returns 1
// outer.value is 1
// inner.value is 1
writer.set('value', 2);
// writer.get('value') returns 2
// outer.value is 1
// inner.value is 2
});
});
});
setTimeout(function() {
// runs with the default context, because nested contexts have ended
console.log(writer.get('value')); // prints 0
}, 1000);
}
```
## cls.createNamespace(name)
* return: {Namespace}
Each application wanting to use continuation-local values should create its own
namespace. Reading from (or, more significantly, writing to) namespaces that
don't belong to you is a faux pas.
## cls.getNamespace(name)
* return: {Namespace}
Look up an existing namespace.
## cls.destroyNamespace(name)
Dispose of an existing namespace. WARNING: be sure to dispose of any references
to destroyed namespaces in your old code, as contexts associated with them will
no longer be propagated.
## cls.reset()
Completely reset all continuation-local storage namespaces. WARNING: while this
will stop the propagation of values in any existing namespaces, if there are
remaining references to those namespaces in code, the associated storage will
still be reachable, even though the associated state is no longer being updated.
Make sure you clean up any references to destroyed namespaces yourself.
## process.namespaces
* return: dictionary of {Namespace} objects
Continuation-local storage has a performance cost, and so it isn't enabled
until the module is loaded for the first time. Once the module is loaded, the
current set of namespaces is available in `process.namespaces`, so library code
that wants to use continuation-local storage only when it's active should test
for the existence of `process.namespaces`.
## Class: Namespace
Application-specific namespaces group values local to the set of functions
whose calls originate from a callback passed to `namespace.run()` or
`namespace.bind()`.
### namespace.active
* return: the currently active context on a namespace
### namespace.set(key, value)
* return: `value`
Set a value on the current continuation context. Must be set within an active
continuation chain started with `namespace.run()` or `namespace.bind()`.
### namespace.get(key)
* return: the requested value, or `undefined`
Look up a value on the current continuation context. Recursively searches from
the innermost to outermost nested continuation context for a value associated
with a given key. Must be set within an active continuation chain started with
`namespace.run()` or `namespace.bind()`.
### namespace.run(callback)
* return: the context associated with that callback
Create a new context on which values can be set or read. Run all the functions
that are called (either directly, or indirectly through asynchronous functions
that take callbacks themselves) from the provided callback within the scope of
that namespace. The new context is passed as an argument to the callback
when it's called.
### namespace.runAndReturn(callback)
* return: the return value of the callback
Create a new context on which values can be set or read. Run all the functions
that are called (either directly, or indirectly through asynchronous functions
that take callbacks themselves) from the provided callback within the scope of
that namespace. The new context is passed as an argument to the callback
when it's called.
Same as `namespace.run()` but returns the return value of the callback rather
than the context.
### namespace.bind(callback, [context])
* return: a callback wrapped up in a context closure
Bind a function to the specified namespace. Works analogously to
`Function.bind()` or `domain.bind()`. If context is omitted, it will default to
the currently active context in the namespace, or create a new context if none
is currently defined.
### namespace.bindEmitter(emitter)
Bind an EventEmitter to a namespace. Operates similarly to `domain.add`, with a
less generic name and the additional caveat that unlike domains, namespaces
never implicitly bind EventEmitters to themselves when they're created within
the context of an active namespace.
The most likely time you'd want to use this is when you're using Express or
Connect and want to make sure your middleware execution plays nice with CLS, or
are doing other things with HTTP listeners:
```javascript
http.createServer(function (req, res) {
writer.bindEmitter(req);
writer.bindEmitter(res);
// do other stuff, some of which is asynchronous
});
```
### namespace.createContext()
* return: a context cloned from the currently active context
Use this with `namespace.bind()`, if you want to have a fresh context at invocation time,
as opposed to binding time:
```javascript
function doSomething(p) {
console.log("%s = %s", p, ns.get(p));
}
function bindLater(callback) {
return writer.bind(callback, writer.createContext());
}
setInterval(function () {
var bound = bindLater(doSomething);
bound('test');
}, 100);
```
## context
A context is a plain object created using the enclosing context as its prototype.
# copyright & license
See [LICENSE](https://github.com/othiym23/node-continuation-local-storage/blob/master/LICENSE)
for the details of the BSD 2-clause "simplified" license used by
`continuation-local-storage`. This package was developed in 2012-2013 (and is
maintained now) by Forrest L Norvell, [@othiym23](https://github.com/othiym23),
with considerable help from Timothy Caswell,
[@creationix](https://github.com/creationix), working for The Node Firm. This
work was underwritten by New Relic for use in their Node.js instrumentation
agent, so maybe give that a look if you have some Node.js
performance-monitoring needs.
[timer functions]: https://nodejs.org/api/timers.html
[setImmediate]: https://nodejs.org/api/timers.html#timers_setimmediate_callback_arg
[setTimeout]: https://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg
[setInterval]: https://nodejs.org/api/timers.html#timers_setinterval_callback_delay_arg
[cps]: http://en.wikipedia.org/wiki/Continuation-passing_style
+212
View File
@@ -0,0 +1,212 @@
'use strict';
var assert = require('assert');
var wrapEmitter = require('emitter-listener');
/*
*
* CONSTANTS
*
*/
var CONTEXTS_SYMBOL = 'cls@contexts';
var ERROR_SYMBOL = 'error@context';
// load polyfill if native support is unavailable
if (!process.addAsyncListener) require('async-listener');
function Namespace(name) {
this.name = name;
// changed in 2.7: no default context
this.active = null;
this._set = [];
this.id = null;
}
Namespace.prototype.set = function (key, value) {
if (!this.active) {
throw new Error("No context available. ns.run() or ns.bind() must be called first.");
}
this.active[key] = value;
return value;
};
Namespace.prototype.get = function (key) {
if (!this.active) return undefined;
return this.active[key];
};
Namespace.prototype.createContext = function () {
return Object.create(this.active);
};
Namespace.prototype.run = function (fn) {
var context = this.createContext();
this.enter(context);
try {
fn(context);
return context;
}
catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;
}
throw exception;
}
finally {
this.exit(context);
}
};
Namespace.prototype.runAndReturn = function (fn) {
var value;
this.run(function (context) {
value = fn(context);
});
return value;
};
Namespace.prototype.bind = function (fn, context) {
if (!context) {
if (!this.active) {
context = this.createContext();
}
else {
context = this.active;
}
}
var self = this;
return function () {
self.enter(context);
try {
return fn.apply(this, arguments);
}
catch (exception) {
if (exception) {
exception[ERROR_SYMBOL] = context;
}
throw exception;
}
finally {
self.exit(context);
}
};
};
Namespace.prototype.enter = function (context) {
assert.ok(context, "context must be provided for entering");
this._set.push(this.active);
this.active = context;
};
Namespace.prototype.exit = function (context) {
assert.ok(context, "context must be provided for exiting");
// Fast path for most exits that are at the top of the stack
if (this.active === context) {
assert.ok(this._set.length, "can't remove top context");
this.active = this._set.pop();
return;
}
// Fast search in the stack using lastIndexOf
var index = this._set.lastIndexOf(context);
assert.ok(index >= 0, "context not currently entered; can't exit");
assert.ok(index, "can't remove top context");
this._set.splice(index, 1);
};
Namespace.prototype.bindEmitter = function (emitter) {
assert.ok(emitter.on && emitter.addListener && emitter.emit, "can only bind real EEs");
var namespace = this;
var thisSymbol = 'context@' + this.name;
// Capture the context active at the time the emitter is bound.
function attach(listener) {
if (!listener) return;
if (!listener[CONTEXTS_SYMBOL]) listener[CONTEXTS_SYMBOL] = Object.create(null);
listener[CONTEXTS_SYMBOL][thisSymbol] = {
namespace : namespace,
context : namespace.active
};
}
// At emit time, bind the listener within the correct context.
function bind(unwrapped) {
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) return unwrapped;
var wrapped = unwrapped;
var contexts = unwrapped[CONTEXTS_SYMBOL];
Object.keys(contexts).forEach(function (name) {
var thunk = contexts[name];
wrapped = thunk.namespace.bind(wrapped, thunk.context);
});
return wrapped;
}
wrapEmitter(emitter, attach, bind);
};
/**
* If an error comes out of a namespace, it will have a context attached to it.
* This function knows how to find it.
*
* @param {Error} exception Possibly annotated error.
*/
Namespace.prototype.fromException = function (exception) {
return exception[ERROR_SYMBOL];
};
function get(name) {
return process.namespaces[name];
}
function create(name) {
assert.ok(name, "namespace must be given a name!");
var namespace = new Namespace(name);
namespace.id = process.addAsyncListener({
create : function () { return namespace.active; },
before : function (context, storage) { if (storage) namespace.enter(storage); },
after : function (context, storage) { if (storage) namespace.exit(storage); },
error : function (storage) { if (storage) namespace.exit(storage); }
});
process.namespaces[name] = namespace;
return namespace;
}
function destroy(name) {
var namespace = get(name);
assert.ok(namespace, "can't delete nonexistent namespace!");
assert.ok(namespace.id, "don't assign to process.namespaces directly!");
process.removeAsyncListener(namespace.id);
process.namespaces[name] = null;
}
function reset() {
// must unregister async listeners
if (process.namespaces) {
Object.keys(process.namespaces).forEach(function (name) {
destroy(name);
});
}
process.namespaces = Object.create(null);
}
if (!process.namespaces) reset(); // call immediately to set up
module.exports = {
getNamespace : get,
createNamespace : create,
destroyNamespace : destroy,
reset : reset
};
+38
View File
@@ -0,0 +1,38 @@
{
"name": "continuation-local-storage",
"version": "3.2.1",
"description": "userland implementation of https://github.com/joyent/node/issues/5243",
"main": "context.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "tap test/*.tap.js"
},
"repository": {
"type": "git",
"url": "https://github.com/othiym23/node-continuation-local-storage.git"
},
"keywords": [
"threading",
"shared",
"context",
"domains",
"tracing",
"logging"
],
"author": "Forrest L Norvell <ogd@aoaioxxysz.net>",
"contributors": [
"Tim Caswell <tim@creationix.com>",
"Forrest L Norvell <ogd@aoaioxxysz.net>"
],
"license": "BSD-2-Clause",
"devDependencies": {
"semver": "^5.4.1",
"tap": "^10.3.1"
},
"dependencies": {
"async-listener": "^0.6.0",
"emitter-listener": "^1.1.1"
}
}
+18
View File
@@ -0,0 +1,18 @@
'use strict';
var tap = require('tap')
, test = tap.test
, createNamespace = require('../context.js').createNamespace
;
test("asynchronously propagating state with local-context-domains", function (t) {
t.plan(2);
var namespace = createNamespace('namespace');
t.ok(process.namespaces.namespace, "namespace has been created");
namespace.run(function () {
namespace.set('test', 1337);
t.equal(namespace.get('test'), 1337, "namespace is working");
});
});
@@ -0,0 +1,23 @@
var test = require('tap').test
, cls = require('../context.js')
;
test("minimized test case that caused #6011 patch to fail", function (t) {
t.plan(3);
console.log('+');
// when the flaw was in the patch, commenting out this line would fix things:
process.nextTick(function () { console.log('!'); });
var n = cls.createNamespace("test");
t.ok(!n.get('state'), "state should not yet be visible");
n.run(function () {
n.set('state', true);
t.ok(n.get('state'), "state should be visible");
process.nextTick(function () {
t.ok(n.get('state'), "state should be visible");
});
});
});
+378
View File
@@ -0,0 +1,378 @@
'use strict';
var test = require('tap').test
, EventEmitter = require('events').EventEmitter
, cls = require('../context.js')
;
test("event emitters bound to CLS context", function (t) {
t.plan(13);
t.test("handler registered in context, emit out of context", function (t) {
t.plan(1);
var n = cls.createNamespace('in')
, ee = new EventEmitter()
;
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(ee);
ee.on('event', function () {
t.equal(n.get('value'), 'hello', "value still set in EE.");
cls.destroyNamespace('in');
});
});
ee.emit('event');
});
t.test("once handler registered in context", function (t) {
t.plan(1);
var n = cls.createNamespace('inOnce')
, ee = new EventEmitter()
;
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(ee);
ee.once('event', function () {
t.equal(n.get('value'), 'hello', "value still set in EE.");
cls.destroyNamespace('inOnce');
});
});
ee.emit('event');
});
t.test("handler registered out of context, emit in context", function (t) {
t.plan(1);
var n = cls.createNamespace('out')
, ee = new EventEmitter()
;
ee.on('event', function () {
t.equal(n.get('value'), 'hello', "value still set in EE.");
cls.destroyNamespace('out');
});
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(ee);
ee.emit('event');
});
});
t.test("once handler registered out of context", function (t) {
t.plan(1);
var n = cls.createNamespace('outOnce')
, ee = new EventEmitter()
;
ee.once('event', function () {
t.equal(n.get('value'), 'hello', "value still set in EE.");
cls.destroyNamespace('outOnce');
});
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(ee);
ee.emit('event');
});
});
t.test("handler registered out of context, emit out of context", function (t) {
t.plan(1);
var n = cls.createNamespace('out')
, ee = new EventEmitter()
;
ee.on('event', function () {
t.equal(n.get('value'), undefined, "no context.");
cls.destroyNamespace('out');
});
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(ee);
});
ee.emit('event');
});
t.test("once handler registered out of context on Readable", function (t) {
var Readable = require('stream').Readable;
if (Readable) {
t.plan(12);
var n = cls.createNamespace('outOnceReadable')
, re = new Readable()
;
re._read = function () {};
t.ok(n.name, "namespace has a name");
t.equal(n.name, 'outOnceReadable', "namespace has a name");
re.once('data', function (data) {
t.equal(n.get('value'), 'hello', "value still set in EE");
t.equal(data, 'blah', "emit still works");
cls.destroyNamespace('outOnceReadable');
});
n.run(function () {
n.set('value', 'hello');
t.notOk(re.emit.__wrapped, "emit is not wrapped");
t.notOk(re.on.__wrapped, "on is not wrapped");
t.notOk(re.addListener.__wrapped, "addListener is not wrapped");
n.bindEmitter(re);
t.ok(re.emit.__wrapped, "emit is wrapped");
t.ok(re.on.__wrapped, "on is wrapped");
t.ok(re.addListener.__wrapped, "addListener is wrapped");
t.equal(typeof re._events.data, 'function', 'only the one data listener');
t.notOk(re._events.data['context@outOnceReadable'], "context isn't on listener");
re.emit('data', 'blah');
});
}
else {
t.comment("this test requires node 0.10+");
t.end();
}
});
t.test("emitter with newListener that removes handler", function (t) {
t.plan(3);
var n = cls.createNamespace('newListener')
, ee = new EventEmitter()
;
// add monkeypatching to ee
n.bindEmitter(ee);
function listen() {
ee.on('data', function (chunk) {
t.equal(chunk, 'chunk', 'listener still works');
});
}
ee.on('newListener', function handler(event) {
if (event !== 'data') return;
this.removeListener('newListener', handler);
t.notOk(this.listeners('newListener').length, 'newListener was removed');
process.nextTick(listen);
});
ee.on('drain', function (chunk) {
process.nextTick(function () {
ee.emit('data', chunk);
});
});
ee.on('data', function (chunk) {
t.equal(chunk, 'chunk', 'got data event');
cls.destroyNamespace('newListener');
});
ee.emit('drain', 'chunk');
});
t.test("handler registered in context on Readable", function (t) {
var Readable = require('stream').Readable;
if (Readable) {
t.plan(12);
var n = cls.createNamespace('outOnReadable')
, re = new Readable()
;
re._read = function () {};
t.ok(n.name, "namespace has a name");
t.equal(n.name, 'outOnReadable', "namespace has a name");
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(re);
t.ok(re.emit.__wrapped, "emit is wrapped");
t.ok(re.on.__wrapped, "on is wrapped");
t.ok(re.addListener.__wrapped, "addListener is wrapped");
re.on('data', function (data) {
t.equal(n.get('value'), 'hello', "value still set in EE");
t.equal(data, 'blah', "emit still works");
cls.destroyNamespace('outOnReadable');
});
});
t.ok(re.emit.__wrapped, "emit is still wrapped");
t.ok(re.on.__wrapped, "on is still wrapped");
t.ok(re.addListener.__wrapped, "addListener is still wrapped");
t.equal(typeof re._events.data, 'function', 'only the one data listener');
t.ok(re._events.data['cls@contexts']['context@outOnReadable'],
"context is bound to listener");
re.emit('data', 'blah');
}
else {
t.comment("this test requires node 0.10+");
t.end();
}
});
t.test("handler added but used entirely out of context", function (t) {
t.plan(2);
var n = cls.createNamespace('none')
, ee = new EventEmitter()
;
n.run(function () {
n.set('value', 'hello');
n.bindEmitter(ee);
});
ee.on('event', function () {
t.ok(n, "n is set");
t.notOk(n.get('value'), "value shouldn't be visible");
cls.destroyNamespace('none');
});
ee.emit('event');
});
t.test("handler added but no listeners registered", function (t) {
t.plan(3);
var http = require('http')
, n = cls.createNamespace('no_listener')
;
// only fails on Node < 0.10
var server = http.createServer(function (req, res) {
n.bindEmitter(req);
t.doesNotThrow(function () {
req.emit('event');
});
res.writeHead(200, {"Content-Length" : 4});
res.end('WORD');
});
server.listen(8080);
http.get('http://localhost:8080/', function (res) {
t.equal(res.statusCode, 200, "request came back OK");
res.setEncoding('ascii');
res.on('data', function (body) {
t.equal(body, 'WORD');
server.close();
cls.destroyNamespace('no_listener');
});
});
});
t.test("listener with parameters added but not bound to context", function (t) {
t.plan(2);
var ee = new EventEmitter()
, n = cls.createNamespace('param_list')
;
function sent(value) {
t.equal(value, 3, "sent value is correct");
cls.destroyNamespace('param_list');
}
ee.on('send', sent);
n.bindEmitter(ee);
t.doesNotThrow(function () {
ee.emit('send', 3);
});
});
t.test("listener that throws doesn't leave removeListener wrapped", function (t) {
t.plan(4);
var ee = new EventEmitter()
, n = cls.createNamespace('kaboom')
;
n.bindEmitter(ee);
function kaboom() {
throw new Error('whoops');
}
n.run(function () {
ee.on('bad', kaboom);
t.throws(function () { ee.emit('bad'); });
t.equal(typeof ee.removeListener, 'function', 'removeListener is still there');
t.notOk(ee.removeListener.__wrapped, "removeListener got unwrapped");
t.equal(ee._events.bad, kaboom, "listener isn't still bound");
cls.destroyNamespace('kaboom');
});
});
t.test("emitter bound to multiple namespaces handles them correctly", function (t) {
t.plan(8);
var ee = new EventEmitter()
, ns1 = cls.createNamespace('1')
, ns2 = cls.createNamespace('2')
;
// emulate an incoming data emitter
setTimeout(function () {
ee.emit('data', 'hi');
}, 10);
t.doesNotThrow(function () { ns1.bindEmitter(ee); });
t.doesNotThrow(function () { ns2.bindEmitter(ee); });
ns1.run(function () {
ns2.run(function () {
ns1.set('name', 'tom1');
ns2.set('name', 'tom2');
t.doesNotThrow(function () { ns1.bindEmitter(ee); });
t.doesNotThrow(function () { ns2.bindEmitter(ee); });
ns1.run(function () {
process.nextTick(function () {
t.equal(ns1.get('name'), 'tom1', "ns1 value correct");
t.equal(ns2.get('name'), 'tom2', "ns2 value correct");
ns1.set('name', 'bob');
ns2.set('name', 'alice');
ee.on('data', function () {
t.equal(ns1.get('name'), 'bob', "ns1 value bound onto emitter");
t.equal(ns2.get('name'), 'alice', "ns2 value bound onto emitter");
});
});
});
});
});
});
});
+45
View File
@@ -0,0 +1,45 @@
'use strict';
// stdlib
var tap = require('tap');
var test = tap.test;
var EventEmitter = require('events').EventEmitter;
// module under test
var context = require('../context.js');
// multiple contexts in use
var tracer = context.createNamespace('tracer');
function Trace(harvester) {
this.harvester = harvester;
}
Trace.prototype.runHandler = function (callback) {
var wrapped = tracer.bind(function () {
callback();
this.harvester.emit('finished', tracer.get('transaction'));
}.bind(this));
wrapped();
};
test("simple tracer built on contexts", function (t) {
t.plan(6);
var harvester = new EventEmitter();
var trace = new Trace(harvester);
harvester.on('finished', function (transaction) {
t.ok(transaction, "transaction should have been passed in");
t.equal(transaction.status, 'ok', "transaction should have finished OK");
t.equal(Object.keys(process.namespaces).length, 1, "Should only have one namespace.");
});
trace.runHandler(function inScope() {
t.ok(tracer.active, "tracer should have an active context");
tracer.set('transaction', {status : 'ok'});
t.ok(tracer.get('transaction'), "can retrieve newly-set value");
t.equal(tracer.get('transaction').status, 'ok', "value should be correct");
});
});
+82
View File
@@ -0,0 +1,82 @@
'use strict';
var tap = require('tap')
, semver = require('semver')
, test = tap.test
, createNamespace = require('../context.js').createNamespace
;
var crypto;
try { crypto = require('crypto'); }
catch (err) {}
if (crypto) {
test("continuation-local state with crypto.randomBytes", function (t) {
t.plan(1);
var namespace = createNamespace('namespace');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("randomBytes", function (t) {
namespace.run(function () {
namespace.set('test', 42);
crypto.randomBytes(100, function (err) {
if (err) throw err;
t.equal(namespace.get('test'), 42, "mutated state was preserved");
t.end();
});
});
});
});
});
test("continuation-local state with crypto.pseudoRandomBytes", function (t) {
t.plan(1);
var namespace = createNamespace('namespace');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("pseudoRandomBytes", function (t) {
namespace.run(function () {
namespace.set('test', 42);
crypto.pseudoRandomBytes(100, function (err) {
if (err) throw err;
t.equal(namespace.get('test'), 42, "mutated state was preserved");
t.end();
});
});
});
});
});
test("continuation-local state with crypto.pbkdf2", function (t) {
t.plan(1);
var namespace = createNamespace('namespace');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("pbkdf2", function (t) {
namespace.run(function () {
namespace.set('test', 42);
// this API changed after 0.10.0, and errors if digest is missing after v6
if (semver.gte(process.version, "0.12.0")) {
crypto.pbkdf2("s3cr3tz", "451243", 10, 40, "sha512", function (err) {
if (err) throw err;
t.equal(namespace.get('test'), 42, "mutated state was preserved");
t.end();
});
} else {
crypto.pbkdf2("s3cr3tz", "451243", 10, 40, function (err) {
if (err) throw err;
t.equal(namespace.get('test'), 42, "mutated state was preserved");
t.end();
});
}
});
});
});
});
}
+206
View File
@@ -0,0 +1,206 @@
'use strict';
var dns = require('dns')
, tap = require('tap')
, test = tap.test
, createNamespace = require('../context.js').createNamespace
;
test("continuation-local state with MakeCallback and DNS module", function (t) {
t.plan(11);
var namespace = createNamespace('dns');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("dns.lookup", function (t) {
namespace.run(function () {
namespace.set('test', 808);
t.equal(namespace.get('test'), 808, "state has been mutated");
dns.lookup('www.newrelic.com', 4, function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 808,
"mutated state has persisted to dns.lookup's callback");
t.end();
});
});
});
t.test("dns.resolve", function (t) {
namespace.run(function () {
namespace.set('test', 909);
t.equal(namespace.get('test'), 909, "state has been mutated");
dns.resolve('newrelic.com', 'NS', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 909,
"mutated state has persisted to dns.resolve's callback");
t.end();
});
});
});
t.test("dns.resolve4", function (t) {
namespace.run(function () {
namespace.set('test', 303);
t.equal(namespace.get('test'), 303, "state has been mutated");
dns.resolve4('www.newrelic.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 303,
"mutated state has persisted to dns.resolve4's callback");
t.end();
});
});
});
t.test("dns.resolve6", function (t) {
namespace.run(function () {
namespace.set('test', 101);
t.equal(namespace.get('test'), 101, "state has been mutated");
dns.resolve6('google.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 101,
"mutated state has persisted to dns.resolve6's callback");
t.end();
});
});
});
t.test("dns.resolveCname", function (t) {
namespace.run(function () {
namespace.set('test', 212);
t.equal(namespace.get('test'), 212, "state has been mutated");
dns.resolveCname('mail.newrelic.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 212,
"mutated state has persisted to dns.resolveCname's callback");
t.end();
});
});
});
t.test("dns.resolveMx", function (t) {
namespace.run(function () {
namespace.set('test', 707);
t.equal(namespace.get('test'), 707, "state has been mutated");
dns.resolveMx('newrelic.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 707,
"mutated state has persisted to dns.resolveMx's callback");
t.end();
});
});
});
t.test("dns.resolveNs", function (t) {
namespace.run(function () {
namespace.set('test', 717);
t.equal(namespace.get('test'), 717, "state has been mutated");
dns.resolveNs('newrelic.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 717,
"mutated state has persisted to dns.resolveNs's callback");
t.end();
});
});
});
t.test("dns.resolveTxt", function (t) {
namespace.run(function () {
namespace.set('test', 2020);
t.equal(namespace.get('test'), 2020, "state has been mutated");
dns.resolveTxt('newrelic.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 2020,
"mutated state has persisted to dns.resolveTxt's callback");
t.end();
});
});
});
t.test("dns.resolveSrv", function (t) {
namespace.run(function () {
namespace.set('test', 9000);
t.equal(namespace.get('test'), 9000, "state has been mutated");
dns.resolveSrv('_xmpp-server._tcp.google.com', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 9000,
"mutated state has persisted to dns.resolveSrv's callback");
t.end();
});
});
});
t.test("dns.resolveNaptr", function (t) {
// dns.resolveNaptr only in Node > 0.9.x
if (!dns.resolveNaptr) return t.end();
namespace.run(function () {
namespace.set('test', 'Polysix');
t.equal(namespace.get('test'), 'Polysix', "state has been mutated");
dns.resolveNaptr('columbia.edu', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 'Polysix',
"mutated state has persisted to dns.resolveNaptr's callback");
t.end();
});
});
});
t.test("dns.reverse", function (t) {
namespace.run(function () {
namespace.set('test', 1000);
t.equal(namespace.get('test'), 1000, "state has been mutated");
dns.reverse('204.93.223.144', function (err, addresses) {
t.notOk(err, "lookup succeeded");
t.ok(addresses.length > 0, "some results were found");
t.equal(namespace.get('test'), 1000,
"mutated state has persisted to dns.reverse's callback");
t.end();
});
});
});
});
});
+144
View File
@@ -0,0 +1,144 @@
'use strict';
var test = require('tap').test
, cls = require('../context.js')
, domain = require('domain')
;
test("continuation-local storage glue with a throw in the continuation chain",
function (t) {
var namespace = cls.createNamespace('test');
namespace.run(function () {
var d = domain.create();
namespace.set('outer', true);
namespace.bindEmitter(d);
d.on('error', function (blerg) {
t.equal(blerg.message, "explicitly nonlocal exit", "got the expected exception");
t.ok(namespace.get('outer'), "outer context is still active");
t.notOk(namespace.get('inner'), "inner context should have been exited by throw");
t.equal(namespace._set.length, 1, "should be back to outer state");
cls.destroyNamespace('test');
t.end();
});
// tap is only trying to help
process.nextTick(d.bind(function () {
t.ok(namespace.get('outer'), "outer mutation worked");
t.notOk(namespace.get('inner'), "inner mutation hasn't happened yet");
namespace.run(function () {
namespace.set('inner', true);
throw new Error("explicitly nonlocal exit");
});
}));
});
});
test("synchronous throw attaches the context", function (t) {
t.plan(3);
var namespace = cls.createNamespace('cls@synchronous');
namespace.run(function () {
namespace.set('value', 'transaction clear');
try {
namespace.run(function () {
namespace.set('value', 'transaction set');
throw new Error('cls@synchronous explosion');
});
}
catch (e) {
t.ok(namespace.fromException(e), "context was attached to error");
t.equal(namespace.fromException(e)['value'], 'transaction set',
"found the inner value");
}
t.equal(namespace.get('value'), 'transaction clear', "everything was reset");
});
cls.destroyNamespace('cls@synchronous');
});
test("synchronous throw checks if error exists", function (t) {
t.plan(2);
var namespace = cls.createNamespace('cls@synchronous-null-error');
namespace.run(function () {
namespace.set('value', 'transaction clear');
try {
namespace.run(function () {
namespace.set('value', 'transaction set');
throw null;
});
}
catch (e) {
// as we had a null error, cls couldn't set the new inner value
t.equal(namespace.get('value'), 'transaction clear', 'from outer value');
}
t.equal(namespace.get('value'), 'transaction clear', "everything was reset");
});
cls.destroyNamespace('cls@synchronous-null-error');
});
test("throw in process.nextTick attaches the context", function (t) {
t.plan(3);
var namespace = cls.createNamespace('cls@nexttick');
var d = domain.create();
namespace.bindEmitter(d);
d.on('error', function (e) {
t.ok(namespace.fromException(e), "context was attached to error");
t.equal(namespace.fromException(e)['value'], 'transaction set',
"found the inner value");
cls.destroyNamespace('cls@nexttick');
});
namespace.run(function () {
namespace.set('value', 'transaction clear');
// tap is only trying to help
process.nextTick(d.bind(function () {
namespace.run(function () {
namespace.set('value', 'transaction set');
throw new Error("cls@nexttick explosion");
});
}));
t.equal(namespace.get('value'), 'transaction clear', "everything was reset");
});
});
test("throw in setTimeout attaches the context", function (t) {
t.plan(3);
var namespace = cls.createNamespace('cls@setTimeout');
var d = domain.create();
namespace.bindEmitter(d);
d.on('error', function (e) {
t.ok(namespace.fromException(e), "context was attached to error");
t.equal(namespace.fromException(e)['value'], 'transaction set',
"found the inner value");
cls.destroyNamespace('cls@setTimeout');
});
namespace.run(function () {
namespace.set('value', 'transaction clear');
// tap is only trying to help
setTimeout(d.bind(function () {
namespace.run(function () {
namespace.set('value', 'transaction set');
throw new Error("cls@setTimeout explosion");
});
}));
t.equal(namespace.get('value'), 'transaction clear', "everything was reset");
});
});
+892
View File
@@ -0,0 +1,892 @@
'use strict';
var createNamespace = require('../context.js').createNamespace
, fs = require('fs')
, path = require('path')
, exec = require('child_process').exec
, tap = require('tap')
, test = tap.test
;
// CONSTANTS
var FILENAME = '__testfile'
, DIRNAME = '__TESTDIR'
, LINKNAME = '__testlink'
, HARDLINKNAME = '__testhardlink'
;
function createFile(assert) {
var contents = new Buffer("UHOH")
, file = fs.openSync(FILENAME, 'w')
, written = fs.writeSync(file, contents, 0, contents.length, 0)
;
assert.equals(written, contents.length, "whole buffer was written");
var rc = fs.closeSync(file);
// need this here to avoid dealing with umask complications
fs.chmodSync(FILENAME, '0666');
return rc;
}
function deleteFile() { return fs.unlinkSync(FILENAME); }
function createLink(assert) {
createFile(assert);
fs.symlinkSync(FILENAME, LINKNAME);
if (fs.lchmodSync) {
// This function only exists on BSD systems (like OSX)
fs.lchmodSync(LINKNAME, '0777');
}
}
function deleteLink() {
fs.unlinkSync(LINKNAME);
return deleteFile();
}
function createDirectory(assert) {
fs.mkdirSync(DIRNAME);
assert.ok(fs.existsSync(DIRNAME), "directory was created");
}
function deleteDirectory() { return fs.rmdirSync(DIRNAME); }
function mapIds(username, groupname, callback) {
if (!callback) throw new Error("mapIds requires callback");
if (!username) return callback(new Error("mapIds requires username"));
if (!groupname) return callback(new Error("mapIds requires groupname"));
exec('id -u ' + username, function (error, stdout, stderr) {
if (error) return callback(error);
if (stderr) return callback(new Error(stderr));
var uid = +stdout;
exec('id -g ' + groupname, function (error, stdout, stderr) {
if (error) return callback(error);
if (stderr) return callback(new Error(stderr));
var gid = +stdout;
callback(null, uid, gid);
});
});
}
test("continuation-local state with MakeCallback and fs module", function (t) {
t.plan(33);
var namespace = createNamespace('fs');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("fs.rename", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'rename');
t.equal(namespace.get('test'), 'rename', "state has been mutated");
fs.rename(FILENAME, '__renamed', function (error) {
t.notOk(error, "renaming shouldn't error");
t.equal(namespace.get('test'), 'rename',
"mutated state has persisted to fs.rename's callback");
fs.unlinkSync('__renamed');
t.end();
});
});
});
t.test("fs.truncate", function (t) {
// truncate -> ftruncate in Node > 0.8.x
if (!fs.ftruncate) return t.end();
createFile(t);
namespace.run(function () {
namespace.set('test', 'truncate');
t.equal(namespace.get('test'), 'truncate', "state has been mutated");
fs.truncate(FILENAME, 0, function (error) {
t.notOk(error, "truncation shouldn't error");
var stats = fs.statSync(FILENAME);
t.equal(stats.size, 0, "file has been truncated");
t.equal(namespace.get('test'), 'truncate',
"mutated state has persisted to fs.truncate's callback");
deleteFile();
t.end();
});
});
});
t.test("fs.ftruncate", function (t) {
createFile(t);
// truncate -> ftruncate in Node > 0.8.x
var truncate = fs.ftruncate ? fs.ftruncate : fs.truncate;
namespace.run(function () {
namespace.set('test', 'ftruncate');
t.equal(namespace.get('test'), 'ftruncate', "state has been mutated");
var file = fs.openSync(FILENAME, 'w');
truncate(file, 0, function (error) {
t.notOk(error, "truncation shouldn't error");
fs.closeSync(file);
var stats = fs.statSync(FILENAME);
t.equal(stats.size, 0, "file has been truncated");
t.equal(namespace.get('test'), 'ftruncate',
"mutated state has persisted to fs.ftruncate's callback");
deleteFile();
t.end();
});
});
});
t.test("fs.chown", function (t) {
createFile(t);
mapIds('daemon', 'daemon', function (error, uid, gid) {
t.notOk(error, "looking up uid & gid shouldn't error");
t.ok(uid, "uid for daemon was found");
t.ok(gid, "gid for daemon was found");
namespace.run(function () {
namespace.set('test', 'chown');
t.equal(namespace.get('test'), 'chown', "state has been mutated");
fs.chown(FILENAME, uid, gid, function (error) {
t.ok(error, "changing ownership will error for non-root users");
t.equal(namespace.get('test'), 'chown',
"mutated state has persisted to fs.chown's callback");
deleteFile();
t.end();
});
});
});
});
t.test("fs.fchown", function (t) {
createFile(t);
mapIds('daemon', 'daemon', function (error, uid, gid) {
t.notOk(error, "looking up uid & gid shouldn't error");
t.ok(uid, "uid for daemon was found");
t.ok(gid, "gid for daemon was found");
namespace.run(function () {
namespace.set('test', 'fchown');
t.equal(namespace.get('test'), 'fchown', "state has been mutated");
var file = fs.openSync(FILENAME, 'w');
fs.fchown(file, uid, gid, function (error) {
t.ok(error, "changing ownership will error for non-root users");
t.equal(namespace.get('test'), 'fchown',
"mutated state has persisted to fs.fchown's callback");
fs.closeSync(file);
deleteFile();
t.end();
});
});
});
});
t.test("fs.lchown", function (t) {
if (!fs.lchown) return t.end();
createLink(t);
mapIds('daemon', 'daemon', function (error, uid, gid) {
t.notOk(error, "looking up uid & gid shouldn't error");
t.ok(uid, "uid for daemon was found");
t.ok(gid, "gid for daemon was found");
namespace.run(function () {
namespace.set('test', 'lchown');
t.equal(namespace.get('test'), 'lchown', "state has been mutated");
fs.lchown(LINKNAME, uid, gid, function (error) {
t.ok(error, "changing ownership will error for non-root users");
t.equal(namespace.get('test'), 'lchown',
"mutated state has persisted to fs.lchown's callback");
deleteLink();
t.end();
});
});
});
});
t.test("fs.chmod", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'chmod');
t.equal(namespace.get('test'), 'chmod', "state has been mutated");
fs.chmod(FILENAME, '0700', function (error) {
t.notOk(error, "changing mode shouldn't error");
t.equal(namespace.get('test'), 'chmod',
"mutated state has persisted to fs.chmod's callback");
var stats = fs.statSync(FILENAME);
t.equal(stats.mode.toString(8), '100700', "extra access bits are stripped");
deleteFile();
t.end();
});
});
});
t.test("fs.fchmod", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'fchmod');
t.equal(namespace.get('test'), 'fchmod', "state has been mutated");
var file = fs.openSync(FILENAME, 'w+');
fs.fchmod(file, '0700', function (error) {
t.notOk(error, "changing mode shouldn't error");
t.equal(namespace.get('test'), 'fchmod',
"mutated state has persisted to fs.fchmod's callback");
fs.closeSync(file);
var stats = fs.statSync(FILENAME);
t.equal(stats.mode.toString(8), '100700', "extra access bits are stripped");
deleteFile();
t.end();
});
});
});
t.test("fs.lchmod", function (t) {
if (!fs.lchmod) return t.end();
createLink(t);
namespace.run(function () {
namespace.set('test', 'lchmod');
t.equal(namespace.get('test'), 'lchmod', "state has been mutated");
fs.lchmod(LINKNAME, '0700', function (error) {
t.notOk(error, "changing mode shouldn't error");
t.equal(namespace.get('test'), 'lchmod',
"mutated state has persisted to fs.lchmod's callback");
var stats = fs.lstatSync(LINKNAME);
t.equal(stats.mode.toString(8), '120700', "extra access bits are stripped");
deleteLink();
t.end();
});
});
});
t.test("fs.stat", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'stat');
t.equal(namespace.get('test'), 'stat', "state has been mutated");
fs.stat(FILENAME, function (error, stats) {
t.notOk(error, "reading stats shouldn't error");
t.equal(namespace.get('test'), 'stat',
"mutated state has persisted to fs.stat's callback");
t.equal(stats.mode.toString(8), '100666', "permissions should be as created");
deleteFile();
t.end();
});
});
});
t.test("fs.fstat", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'fstat');
t.equal(namespace.get('test'), 'fstat', "state has been mutated");
var file = fs.openSync(FILENAME, 'r');
fs.fstat(file, function (error, stats) {
t.notOk(error, "reading stats shouldn't error");
t.equal(namespace.get('test'), 'fstat',
"mutated state has persisted to fs.fstat's callback");
t.equal(stats.mode.toString(8), '100666', "permissions should be as created");
fs.closeSync(file);
deleteFile();
t.end();
});
});
});
t.test("fs.lstat", function (t) {
createLink(t);
namespace.run(function () {
namespace.set('test', 'lstat');
t.equal(namespace.get('test'), 'lstat', "state has been mutated");
fs.lstat(LINKNAME, function (error, stats) {
t.notOk(error, "reading stats shouldn't error");
t.equal(namespace.get('test'), 'lstat',
"mutated state has persisted to fs.lstat's callback");
t.equal(
stats.mode.toString(8),
'120777',
"permissions should be as created"
);
deleteLink();
t.end();
});
});
});
t.test("fs.link", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'link');
t.equal(namespace.get('test'), 'link', "state has been mutated");
fs.link(FILENAME, HARDLINKNAME, function (error) {
t.notOk(error, "creating a link shouldn't error");
t.equal(namespace.get('test'), 'link',
"mutated state has persisted to fs.link's callback");
var orig = fs.statSync(FILENAME)
, linked = fs.statSync(HARDLINKNAME)
;
t.equal(orig.ino, linked.ino, "entries should point to same file");
t.notOk(fs.unlinkSync(HARDLINKNAME), "link has been removed");
deleteFile();
t.end();
});
});
});
t.test("fs.symlink", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'symlink');
t.equal(namespace.get('test'), 'symlink', "state has been mutated");
fs.symlink(FILENAME, LINKNAME, function (error) {
t.notOk(error, "creating a symlink shouldn't error");
t.equal(namespace.get('test'), 'symlink',
"mutated state has persisted to fs.symlink's callback");
var pointed = fs.readlinkSync(LINKNAME);
t.equal(pointed, FILENAME, "symlink points back to original file");
t.notOk(fs.unlinkSync(LINKNAME), "symlink has been removed");
deleteFile();
t.end();
});
});
});
t.test("fs.readlink", function (t) {
createLink(t);
namespace.run(function () {
namespace.set('test', 'readlink');
t.equal(namespace.get('test'), 'readlink', "state has been mutated");
fs.readlink(LINKNAME, function (error, pointed) {
t.notOk(error, "reading symlink shouldn't error");
t.equal(namespace.get('test'), 'readlink',
"mutated state has persisted to fs.readlink's callback");
t.equal(pointed, FILENAME, "symlink points back to original file");
deleteLink();
t.end();
});
});
});
t.test("fs.unlink", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'unlink');
t.equal(namespace.get('test'), 'unlink', "state has been mutated");
fs.unlink(FILENAME, function (error) {
t.notOk(error, "deleting file shouldn't error");
t.equal(namespace.get('test'), 'unlink',
"mutated state has persisted to fs.unlink's callback");
t.notOk(fs.exists(FILENAME), "file should be gone");
t.end();
});
});
});
t.test("fs.realpath", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'realpath');
t.equal(namespace.get('test'), 'realpath', "state has been mutated");
fs.realpath(FILENAME, function (error, real) {
t.notOk(error, "deleting file shouldn't error");
t.equal(namespace.get('test'), 'realpath',
"mutated state has persisted to fs.realpath's callback");
t.equal(real, path.resolve(FILENAME), "no funny business with the real path");
deleteFile();
t.end();
});
});
});
t.test("fs.mkdir", function (t) {
namespace.run(function () {
namespace.set('test', 'mkdir');
t.equal(namespace.get('test'), 'mkdir', "state has been mutated");
fs.mkdir(DIRNAME, function (error) {
t.notOk(error, "creating directory shouldn't error");
t.equal(namespace.get('test'), 'mkdir',
"mutated state has persisted to fs.mkdir's callback");
t.ok(fs.existsSync(DIRNAME), "directory was created");
fs.rmdirSync(DIRNAME);
t.end();
});
});
});
t.test("fs.rmdir", function (t) {
createDirectory(t);
namespace.run(function () {
namespace.set('test', 'rmdir');
t.equal(namespace.get('test'), 'rmdir', "state has been mutated");
fs.rmdir(DIRNAME, function (error) {
t.notOk(error, "deleting directory shouldn't error");
t.equal(namespace.get('test'), 'rmdir',
"mutated state has persisted to fs.rmdir's callback");
t.notOk(fs.existsSync(DIRNAME), "directory was removed");
t.end();
});
});
});
t.test("fs.readdir", function (t) {
createDirectory(t);
var file1 = fs.openSync(path.join(DIRNAME, 'file1'), 'w');
fs.writeSync(file1, 'one');
fs.closeSync(file1);
var file2 = fs.openSync(path.join(DIRNAME, 'file2'), 'w');
fs.writeSync(file2, 'two');
fs.closeSync(file2);
var file3 = fs.openSync(path.join(DIRNAME, 'file3'), 'w');
fs.writeSync(file3, 'three');
fs.closeSync(file3);
namespace.run(function () {
namespace.set('test', 'readdir');
t.equal(namespace.get('test'), 'readdir', "state has been mutated");
fs.readdir(DIRNAME, function (error, contents) {
t.notOk(error, "reading directory shouldn't error");
t.equal(namespace.get('test'), 'readdir',
"mutated state has persisted to fs.readdir's callback");
t.equal(contents.length, 3, "3 files were found");
fs.unlinkSync(path.join(DIRNAME, 'file1'));
fs.unlinkSync(path.join(DIRNAME, 'file2'));
fs.unlinkSync(path.join(DIRNAME, 'file3'));
deleteDirectory();
t.end();
});
});
});
t.test("fs.watch", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'watch');
t.equal(namespace.get('test'), 'watch', "state has been mutated");
var watcher = fs.watch(FILENAME,
{persistent : false, interval : 200},
function (event) {
t.equal(namespace.get('test'), 'watch',
"mutated state has persisted to fs.watch's callback");
t.equal(event, 'change', "file was changed");
watcher.close();
process.nextTick(function cleanup() {
deleteFile();
t.end();
});
});
setTimeout(function poke() {
fs.writeFileSync(FILENAME, 'still a test');
}, 20);
});
});
t.test("fs.utimes", function (t) {
createFile(t);
/* utimes(2) takes seconds since the epoch, and Date() deals with
* milliseconds. I just want a date some time in the past.
*/
var PASTIME = new Date(Math.floor((Date.now() - 31337) / 1000) * 1000);
namespace.run(function () {
namespace.set('test', 'utimes');
t.equal(namespace.get('test'), 'utimes', "state has been mutated");
var before = fs.statSync(FILENAME);
t.ok(before.atime, "access time of newly-created file set");
t.ok(before.mtime, "modification time of newly-created file set");
fs.utimes(FILENAME, PASTIME, PASTIME, function (error) {
t.notOk(error, "setting utimes shouldn't error");
t.equal(namespace.get('test'), 'utimes',
"mutated state has persisted to fs.utimes's callback");
var after = fs.statSync(FILENAME);
t.deepEqual(after.atime, PASTIME, "access time of newly-created file is reset");
t.deepEqual(after.mtime, PASTIME, "mod time of newly-created file is reset");
t.notDeepEqual(before.atime, after.atime, "access time changed");
t.notDeepEqual(before.mtime, after.mtime, "mod time changed");
deleteFile();
t.end();
});
});
});
t.test("fs.futimes", function (t) {
createFile(t);
/* futimes(2) takes seconds since the epoch, and Date() deals with
* milliseconds. I just want a date some time in the past.
*/
var PASTIME = new Date(Math.floor((Date.now() - 0xb33fd) / 1000) * 1000);
namespace.run(function () {
namespace.set('test', 'futimes');
t.equal(namespace.get('test'), 'futimes', "state has been mutated");
var before = fs.statSync(FILENAME);
t.ok(before.atime, "access time of newly-created file set");
t.ok(before.mtime, "modification time of newly-created file set");
var file = fs.openSync(FILENAME, "w+");
fs.futimes(file, PASTIME, PASTIME, function (error) {
t.notOk(error, "setting futimes shouldn't error");
fs.closeSync(file);
t.equal(namespace.get('test'), 'futimes',
"mutated state has persisted to fs.futimes's callback");
var after = fs.statSync(FILENAME);
t.deepEqual(after.atime, PASTIME, "access time of newly-created file is reset");
t.deepEqual(after.mtime, PASTIME, "mod time of newly-created file is reset");
t.notDeepEqual(before.atime, after.atime, "access time changed");
t.notDeepEqual(before.mtime, after.mtime, "mod time changed");
deleteFile();
t.end();
});
});
});
t.test("fs.fsync", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'fsync');
t.equal(namespace.get('test'), 'fsync', "state has been mutated");
var file = fs.openSync(FILENAME, 'w+');
fs.fsync(file, function (error) {
t.notOk(error, "syncing the file shouldn't error");
t.equal(namespace.get('test'), 'fsync',
"mutated state has persisted to fs.fsync's callback");
fs.closeSync(file);
deleteFile();
t.end();
});
});
});
t.test("fs.open", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'open');
t.equal(namespace.get('test'), 'open', "state has been mutated");
fs.open(FILENAME, 'r', function (error, file) {
t.notOk(error, "opening the file shouldn't error");
t.equal(namespace.get('test'), 'open',
"mutated state has persisted to fs.open's callback");
var contents = new Buffer(4);
fs.readSync(file, contents, 0, 4, 0);
t.equal(contents.toString(), 'UHOH', "contents are still available");
fs.closeSync(file);
deleteFile();
t.end();
});
});
});
t.test("fs.close", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'close');
t.equal(namespace.get('test'), 'close', "state has been mutated");
var file = fs.openSync(FILENAME, 'r');
fs.close(file, function (error) {
t.notOk(error, "closing the file shouldn't error");
t.equal(namespace.get('test'), 'close',
"mutated state has persisted to fs.close's callback");
deleteFile();
t.end();
});
});
});
t.test("fs.read", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'read');
t.equal(namespace.get('test'), 'read', "state has been mutated");
var file = fs.openSync(FILENAME, 'r')
, contents = new Buffer(4)
;
fs.read(file, contents, 0, 4, 0, function (error) {
t.notOk(error, "reading from the file shouldn't error");
t.equal(namespace.get('test'), 'read',
"mutated state has persisted to fs.read's callback");
t.equal(contents.toString(), 'UHOH', "contents are still available");
fs.closeSync(file);
deleteFile();
t.end();
});
});
});
t.test("fs.write", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'write');
t.equal(namespace.get('test'), 'write', "state has been mutated");
var file = fs.openSync(FILENAME, 'w')
, contents = new Buffer('yeap')
;
fs.write(file, contents, 0, 4, 0, function (error) {
t.notOk(error, "writing to the file shouldn't error");
t.equal(namespace.get('test'), 'write',
"mutated state has persisted to fs.write's callback");
fs.closeSync(file);
var readback = fs.readFileSync(FILENAME);
t.equal(readback.toString(), 'yeap', "contents are still available");
deleteFile();
t.end();
});
});
});
t.test("fs.readFile", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'readFile');
t.equal(namespace.get('test'), 'readFile', "state has been mutated");
fs.readFile(FILENAME, function (error, contents) {
t.notOk(error, "reading from the file shouldn't error");
t.equal(namespace.get('test'), 'readFile',
"mutated state has persisted to fs.readFile's callback");
t.equal(contents.toString(), 'UHOH', "contents are still available");
deleteFile();
t.end();
});
});
});
t.test("fs.writeFile", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'writeFile');
t.equal(namespace.get('test'), 'writeFile', "state has been mutated");
fs.writeFile(FILENAME, 'woopwoop', function (error) {
t.notOk(error, "rewriting the file shouldn't error");
t.equal(namespace.get('test'), 'writeFile',
"mutated state has persisted to fs.writeFile's callback");
var readback = fs.readFileSync(FILENAME);
t.equal(readback.toString(), 'woopwoop', "rewritten contents are available");
deleteFile();
t.end();
});
});
});
t.test("fs.appendFile", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'appendFile');
t.equal(namespace.get('test'), 'appendFile', "state has been mutated");
fs.appendFile(FILENAME, '/jk', function (error) {
t.notOk(error, "appending to the file shouldn't error");
t.equal(namespace.get('test'), 'appendFile',
"mutated state has persisted to fs.appendFile's callback");
var readback = fs.readFileSync(FILENAME);
t.equal(readback.toString(), 'UHOH/jk',
"appended contents are still available");
deleteFile();
t.end();
});
});
});
t.test("fs.exists", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'exists');
t.equal(namespace.get('test'), 'exists', "state has been mutated");
fs.exists(FILENAME, function (yep) {
t.equal(namespace.get('test'), 'exists',
"mutated state has persisted to fs.exists's callback");
t.ok(yep, "precreated file does indeed exist.");
fs.exists('NOPENOWAY', function (nope) {
t.equal(namespace.get('test'), 'exists',
"mutated state continues to persist to fs.exists's second callback");
t.notOk(nope, "nonexistent file doesn't exist.");
deleteFile();
t.end();
});
});
});
});
t.test("fs.watchFile", function (t) {
createFile(t);
namespace.run(function () {
namespace.set('test', 'watchFile');
t.equal(namespace.get('test'), 'watchFile', "state has been mutated");
var options = {
persistent: true,
interval: 20
};
fs.watchFile(FILENAME, options, function (before, after) {
t.equal(namespace.get('test'), 'watchFile',
"mutated state has persisted to fs.watchFile's callback");
t.ok(before.ino, "file has an entry");
t.equal(before.ino, after.ino, "file is at the same location");
fs.unwatchFile(FILENAME);
process.nextTick(function () {
deleteFile();
t.end();
});
});
setTimeout(function poke() {
fs.appendFileSync(FILENAME, 'still a test');
}, 20);
});
});
});
});
@@ -0,0 +1,58 @@
'use strict';
var cls = require('../context.js')
, test = require('tap').test
;
function cleanNamespace(name){
if (cls.getNamespace(name)) cls.destroyNamespace(name);
return cls.createNamespace(name);
}
test("interleaved contexts", function (t) {
t.plan(3);
t.test("interleaving with run", function (t) {
t.plan(2);
var ns = cleanNamespace('test');
var ctx = ns.createContext();
ns.enter(ctx);
ns.run(function () {
t.equal(ns._set.length, 2, "2 contexts in the active set");
t.doesNotThrow(function () { ns.exit(ctx); });
});
});
t.test("entering and exiting staggered", function (t) {
t.plan(4);
var ns = cleanNamespace('test');
var ctx1 = ns.createContext();
var ctx2 = ns.createContext();
t.doesNotThrow(function () { ns.enter(ctx1); });
t.doesNotThrow(function () { ns.enter(ctx2); });
t.doesNotThrow(function () { ns.exit(ctx1); });
t.doesNotThrow(function () { ns.exit(ctx2); });
});
t.test("creating, entering and exiting staggered", function (t) {
t.plan(4);
var ns = cleanNamespace('test');
var ctx1 = ns.createContext();
t.doesNotThrow(function () { ns.enter(ctx1); });
var ctx2 = ns.createContext();
t.doesNotThrow(function () { ns.enter(ctx2); });
t.doesNotThrow(function () { ns.exit(ctx1); });
t.doesNotThrow(function () { ns.exit(ctx2); });
});
});
+55
View File
@@ -0,0 +1,55 @@
'use strict';
var test = require('tap').test;
if (!process.addAsyncListener) {
test("overwriting startup.processNextTick", function (t) {
t.plan(2);
t.doesNotThrow(function () { require('../context.js'); });
t.ok(process.nextTick.__wrapped, "should wrap process.nextTick()");
});
test("overwriting domain helpers", function (t) {
// domain helpers were only in 0.10.x
if (!(process._nextDomainTick && process._tickDomainCallback)) {
return t.end();
}
t.plan(2);
t.ok(process._nextDomainTick.__wrapped,
"should wrap process._nextDomainTick()");
t.ok(process._tickDomainCallback.__wrapped,
"should wrap process._tickDomainCallback()");
});
test("overwriting timers", function (t) {
t.plan(2);
var timers = require('timers');
t.ok(timers.setTimeout.__wrapped, "should wrap setTimeout()");
t.ok(timers.setInterval.__wrapped, "should wrap setInterval()");
/* It would be nice to test that monkeypatching preserves the status quo
* ante, but assert thinks setTimeout !== global.setTimeout (why?) and both of
* those are a wrapper around NativeModule.require("timers").setTimeout,
* presumably to try to prevent the kind of "fun" I'm having here.
*/
});
test("overwriting setImmediate", function (t) {
// setTimeout's a johnny-come-lately
if (!global.setImmediate) return t.end();
t.plan(1);
t.ok(require('timers').setImmediate.__wrapped, "should wrap setImmediate()");
/* It would be nice to test that monkeypatching preserves the status quo
* ante, but assert thinks setTimeout !== global.setTimeout (why?) and both of
* those are a wrapper around NativeModule.require("timers").setTimeout,
* presumably to try to prevent the kind of "fun" I'm having here.
*/
});
}
+29
View File
@@ -0,0 +1,29 @@
'use strict';
var tap = require('tap');
var test = tap.test;
var context = require('../context.js');
test("namespace management", function (t) {
t.plan(8);
t.throws(function () { context.createNamespace(); }, "name is required");
var namespace = context.createNamespace('test');
t.ok(namespace, "namespace is returned upon creation");
t.equal(context.getNamespace('test'), namespace, "namespace lookup works");
t.doesNotThrow(function () { context.reset(); }, "allows resetting namespaces");
t.equal(Object.keys(process.namespaces).length, 0, "namespaces have been reset");
namespace = context.createNamespace('another');
t.ok(process.namespaces.another, "namespace is available from global");
t.doesNotThrow(function () { context.destroyNamespace('another'); },
"destroying works");
t.notOk(process.namespaces.another, "namespace has been removed");
});
+73
View File
@@ -0,0 +1,73 @@
'use strict';
var tap = require('tap');
var test = tap.test;
var cls = require('../context.js');
test("nested contexts on a single namespace", function (t) {
t.plan(7);
var namespace = cls.createNamespace("namespace");
namespace.run(function () {
namespace.set("value", 1);
t.equal(namespace.get("value"), 1,
"namespaces have associated data even without contexts.");
namespace.run(function () {
t.equal(namespace.get("value"), 1, "lookup will check enclosing context");
namespace.set("value", 2);
t.equal(namespace.get("value"), 2, "setting works on top-level context");
namespace.run(function () {
t.equal(namespace.get("value"), 2, "lookup will check enclosing context");
namespace.set("value", 3);
t.equal(namespace.get("value"), 3, "setting works on nested context");
});
t.equal(namespace.get("value"), 2,
"should revert to value set in top-level context");
});
t.equal(namespace.get("value"), 1, "namespace retains its outermost value.");
});
});
test("the example from the docs", function (t) {
var writer = cls.createNamespace('writer');
writer.run(function () {
writer.set('value', 0);
t.equal(writer.get('value'), 0, "outer hasn't been entered yet");
function requestHandler() {
writer.run(function (outer) {
t.equal(writer.active, outer, "writer.active == outer");
writer.set('value', 1);
t.equal(writer.get('value'), 1, "writer.active == outer");
t.equal(outer.value, 1, "outer is active");
process.nextTick(function () {
t.equal(writer.active, outer, "writer.active == outer");
t.equal(writer.get('value'), 1, "inner has been entered");
writer.run(function (inner) {
t.equal(writer.active, inner, "writer.active == inner");
writer.set('value', 2);
t.equal(outer.value, 1, "outer is unchanged");
t.equal(inner.value, 2, "inner is active");
t.equal(writer.get('value'), 2, "writer.active == inner");
});
});
});
setTimeout(function () {
t.equal(writer.get('value'), 0, "writer.active == global");
t.end();
}, 100);
}
requestHandler();
});
});
+45
View File
@@ -0,0 +1,45 @@
'use strict';
var net = require('net')
, tap = require('tap')
, test = tap.test
, createNamespace = require('../context').createNamespace
;
test("continuation-local state with net connection", function (t) {
t.plan(4);
var namespace = createNamespace('net');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
var server;
namespace.run(function () {
namespace.set('test', 0x1337);
server = net.createServer(function (socket) {
t.equal(namespace.get('test'), 0x1337, "state has been mutated");
socket.on("data", function () {
t.equal(namespace.get('test'), 0x1337, "state is still preserved");
server.close();
socket.end("GoodBye");
});
});
server.listen(function () {
var address = server.address();
namespace.run(function () {
namespace.set("test", "MONKEY");
var client = net.connect(address.port, function () {
t.equal(namespace.get("test"), "MONKEY",
"state preserved for client connection");
client.write("Hello");
client.on("data", function () {
t.equal(namespace.get("test"), "MONKEY", "state preserved for client data");
t.end();
});
});
});
});
});
});
});
+121
View File
@@ -0,0 +1,121 @@
'use strict';
var tap = require('tap')
, test = tap.test
, createNamespace = require('../context.js').createNamespace
;
test("continuation-local state with promises", function (t) {
t.plan(4);
var namespace = createNamespace('namespace');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("chained promises", function (t) {
if (!global.Promise) return t.end();
namespace.run(function () {
namespace.set('test', 31337);
t.equal(namespace.get('test'), 31337, "state has been mutated");
Promise.resolve()
.then(function () {
t.equal(namespace.get('test'), 31337,
"mutated state has persisted to first continuation");
})
.then(function () {
t.equal(namespace.get('test'), 31337,
"mutated state has persisted to second continuation");
})
.then(function () {
t.equal(namespace.get('test'), 31337,
"mutated state has persisted to third continuation");
t.end();
});
});
});
t.test("chained unwrapped promises", function (t) {
if (!global.Promise) return t.end();
namespace.run(function () {
namespace.set('test', 999);
t.equal(namespace.get('test'), 999, "state has been mutated");
Promise.resolve()
.then(function () {
t.equal(namespace.get('test'), 999,
"mutated state has persisted to first continuation");
return Promise.resolve();
})
.then(function () {
t.equal(namespace.get('test'), 999,
"mutated state has persisted to second continuation");
return Promise.resolve();
})
.then(function () {
t.equal(namespace.get('test'), 999,
"mutated state has persisted to third continuation");
t.end();
});
});
});
t.test("nested promises", function (t) {
if (!global.Promise) return t.end();
namespace.run(function () {
namespace.set('test', 54321);
t.equal(namespace.get('test'), 54321, "state has been mutated");
Promise.resolve()
.then(function () {
t.equal(namespace.get('test'), 54321,
"mutated state has persisted to first continuation");
Promise.resolve()
.then(function () {
t.equal(namespace.get('test'), 54321,
"mutated state has persisted to second continuation");
Promise.resolve()
.then(function () {
t.equal(namespace.get('test'), 54321,
"mutated state has persisted to third continuation");
t.end();
});
});
});
});
});
t.test("forked continuations", function (t) {
if (!global.Promise) return t.end();
namespace.run(function () {
namespace.set('test', 10101);
t.equal(namespace.get('test'), 10101, "state has been mutated");
var promise = Promise.resolve();
promise
.then(function () {
t.equal(namespace.get('test'), 10101,
"mutated state has persisted to first continuation");
});
promise
.then(function () {
t.equal(namespace.get('test'), 10101,
"mutated state has persisted to second continuation");
});
promise
.then(function () {
t.equal(namespace.get('test'), 10101,
"mutated state has persisted to third continuation");
t.end();
});
});
});
});
});
+17
View File
@@ -0,0 +1,17 @@
'use strict';
// I love when a tap.plan() comes together
console.log('1..1');
process.on('uncaughtException', function (err) {
if (err.message === 'oops') {
console.log("ok got expected message: %s", err.message);
}
else {
throw err;
}
});
var cls = require('../context.js');
var ns = cls.createNamespace('x');
ns.run(function () { throw new Error('oops'); });
+40
View File
@@ -0,0 +1,40 @@
'use strict';
// stdlib
var tap = require('tap');
var test = tap.test;
var EventEmitter = require('events').EventEmitter;
// module under test
var context = require('../context.js');
// multiple contexts in use
var tracer = context.createNamespace('tracer');
test("simple tracer built on contexts", function (t) {
t.plan(7);
var harvester = new EventEmitter();
harvester.on('finished', function (transaction) {
t.ok(transaction, "transaction should have been passed in");
t.equal(transaction.status, 'ok', "transaction should have finished OK");
t.equal(Object.keys(process.namespaces).length, 1, "Should only have one namespace.");
});
var returnValue = {};
var returnedValue = tracer.runAndReturn(function(context) {
t.ok(tracer.active, "tracer should have an active context");
tracer.set('transaction', {status : 'ok'});
t.ok(tracer.get('transaction'), "can retrieve newly-set value");
t.equal(tracer.get('transaction').status, 'ok', "value should be correct");
harvester.emit('finished', context.transaction);
return returnValue;
});
t.equal(returnedValue, returnValue, "method should pass through return value of function run in scope");
});
+42
View File
@@ -0,0 +1,42 @@
'use strict';
// stdlib
var tap = require('tap');
var test = tap.test;
var EventEmitter = require('events').EventEmitter;
// module under test
var context = require('../context.js');
// multiple contexts in use
var tracer = context.createNamespace('tracer');
function Trace(harvester) {
this.harvester = harvester;
}
Trace.prototype.runHandler = function (handler) {
var trace = tracer.run(handler);
this.harvester.emit('finished', trace.transaction);
};
test("simple tracer built on contexts", function (t) {
t.plan(6);
var harvester = new EventEmitter();
var trace = new Trace(harvester);
harvester.on('finished', function (transaction) {
t.ok(transaction, "transaction should have been passed in");
t.equal(transaction.status, 'ok', "transaction should have finished OK");
t.equal(Object.keys(process.namespaces).length, 1, "Should only have one namespace.");
});
trace.runHandler(function inScope() {
t.ok(tracer.active, "tracer should have an active context");
tracer.set('transaction', {status : 'ok'});
t.ok(tracer.get('transaction'), "can retrieve newly-set value");
t.equal(tracer.get('transaction').status, 'ok', "value should be correct");
});
});
+76
View File
@@ -0,0 +1,76 @@
'use strict';
var tap = require('tap')
, test = tap.test
, createNamespace = require('../context.js').createNamespace
;
test("continuation-local state with timers", function (t) {
t.plan(4);
var namespace = createNamespace('namespace');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("process.nextTick", function (t) {
namespace.run(function () {
namespace.set('test', 31337);
t.equal(namespace.get('test'), 31337, "state has been mutated");
process.nextTick(function () {
t.equal(namespace.get('test'), 31337,
"mutated state has persisted to process.nextTick's callback");
t.end();
});
});
});
t.test("setImmediate", function (t) {
// setImmediate only in Node > 0.9.x
if (!global.setImmediate) return t.end();
namespace.run(function () {
namespace.set('test', 999);
t.equal(namespace.get('test'), 999, "state has been mutated");
setImmediate(function () {
t.equal(namespace.get('test'), 999,
"mutated state has persisted to setImmediate's callback");
t.end();
});
});
});
t.test("setTimeout", function (t) {
namespace.run(function () {
namespace.set('test', 54321);
t.equal(namespace.get('test'), 54321, "state has been mutated");
setTimeout(function () {
t.equal(namespace.get('test'), 54321,
"mutated state has persisted to setTimeout's callback");
t.end();
});
});
});
t.test("setInterval", function (t) {
namespace.run(function () {
namespace.set('test', 10101);
t.equal(namespace.get('test'), 10101,
"continuation-local state has been mutated");
var ref = setInterval(function () {
t.equal(namespace.get('test'), 10101,
"mutated state has persisted to setInterval's callback");
clearInterval(ref);
t.end();
}, 20);
});
});
});
});
+338
View File
@@ -0,0 +1,338 @@
'use strict';
var EventEmitter = require('events').EventEmitter
, assert = require('assert')
, test = require('tap').test
, cls = require('../context.js')
;
var nextID = 1;
function fresh(name) {
assert.ok(!cls.getNamespace(name), "namespace " + name + " already exists");
return cls.createNamespace(name);
}
function destroy(name) {
return function destroyer(t) {
cls.destroyNamespace(name);
assert.ok(!cls.getNamespace(name), "namespace '" + name + "' should no longer exist");
t.end();
};
}
function runInTransaction(name, fn) {
var namespace = cls.getNamespace(name);
assert(namespace, "namespaces " + name + " doesn't exist");
var context = namespace.createContext();
context.transaction = ++nextID;
process.nextTick(namespace.bind(fn, context));
}
test("asynchronous state propagation", function (t) {
t.plan(24);
t.test("a. async transaction with setTimeout", function (t) {
t.plan(2);
var namespace = fresh('a', this);
function handler() {
t.ok(namespace.get('transaction'), "transaction should be visible");
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('a', function () { setTimeout(handler, 100); });
});
t.test("a. cleanup", destroy('a'));
t.test("b. async transaction with setInterval", function (t) {
t.plan(4);
var namespace = fresh('b', this)
, count = 0
, handle
;
function handler() {
count += 1;
if (count > 2) clearInterval(handle);
t.ok(namespace.get('transaction'), "transaction should be visible");
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('b', function () { handle = setInterval(handler, 50); });
});
t.test("b. cleanup", destroy('b'));
t.test("c. async transaction with process.nextTick", function (t) {
t.plan(2);
var namespace = fresh('c', this);
function handler() {
t.ok(namespace.get('transaction'), "transaction should be visible");
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('c', function () { process.nextTick(handler); });
});
t.test("c. cleanup", destroy('c'));
t.test("d. async transaction with EventEmitter.emit", function (t) {
t.plan(2);
var namespace = fresh('d', this)
, ee = new EventEmitter()
;
function handler() {
t.ok(namespace.get('transaction'), "transaction should be visible");
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('d', function () {
ee.on('transaction', handler);
ee.emit('transaction');
});
});
t.test("d. cleanup", destroy('d'));
t.test("e. two overlapping async transactions with setTimeout", function (t) {
t.plan(6);
var namespace = fresh('e', this)
, first
, second
;
function handler(id) {
t.ok(namespace.get('transaction'), "transaction should be visible");
t.equal(namespace.get('transaction'), id, "transaction matches");
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('e', function () {
first = namespace.get('transaction');
setTimeout(handler.bind(null, first), 100);
});
setTimeout(function () {
runInTransaction('e', function () {
second = namespace.get('transaction');
t.notEqual(first, second, "different transaction IDs");
setTimeout(handler.bind(null, second), 100);
});
}, 25);
});
t.test("e. cleanup", destroy('e'));
t.test("f. two overlapping async transactions with setInterval", function (t) {
t.plan(15);
var namespace = fresh('f', this);
function runInterval() {
var count = 0
, handle
, id
;
function handler() {
count += 1;
if (count > 2) clearInterval(handle);
t.ok(namespace.get('transaction'), "transaction should be visible");
t.equal(id, namespace.get('transaction'), "transaction ID should be immutable");
}
function run() {
t.ok(namespace.get('transaction'), "transaction should have been created");
id = namespace.get('transaction');
handle = setInterval(handler, 50);
}
runInTransaction('f', run);
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInterval(); runInterval();
});
t.test("f. cleanup", destroy('f'));
t.test("g. two overlapping async transactions with process.nextTick", function (t) {
t.plan(6);
var namespace = fresh('g', this)
, first
, second
;
function handler(id) {
var transaction = namespace.get('transaction');
t.ok(transaction, "transaction should be visible");
t.equal(transaction, id, "transaction matches");
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('g', function () {
first = namespace.get('transaction');
process.nextTick(handler.bind(null, first));
});
process.nextTick(function () {
runInTransaction('g', function () {
second = namespace.get('transaction');
t.notEqual(first, second, "different transaction IDs");
process.nextTick(handler.bind(null, second));
});
});
});
t.test("g. cleanup", destroy('g'));
t.test("h. two overlapping async runs with EventEmitter.prototype.emit", function (t) {
t.plan(3);
var namespace = fresh('h', this)
, ee = new EventEmitter()
;
function handler() {
t.ok(namespace.get('transaction'), "transaction should be visible");
}
function lifecycle() {
ee.once('transaction', process.nextTick.bind(process, handler));
ee.emit('transaction');
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('h', lifecycle);
runInTransaction('h', lifecycle);
});
t.test("h. cleanup", destroy('h'));
t.test("i. async transaction with an async sub-call with setTimeout", function (t) {
t.plan(5);
var namespace = fresh('i', this);
function inner(callback) {
setTimeout(function () {
t.ok(namespace.get('transaction'), "transaction should (yep) still be visible");
callback();
}, 50);
}
function outer() {
t.ok(namespace.get('transaction'), "transaction should be visible");
setTimeout(function () {
t.ok(namespace.get('transaction'), "transaction should still be visible");
inner(function () {
t.ok(namespace.get('transaction'), "transaction should even still be visible");
});
}, 50);
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('i', setTimeout.bind(null, outer, 50));
});
t.test("i. cleanup", destroy('i'));
t.test("j. async transaction with an async sub-call with setInterval", function (t) {
t.plan(5);
var namespace = fresh('j', this)
, outerHandle
, innerHandle
;
function inner(callback) {
innerHandle = setInterval(function () {
clearInterval(innerHandle);
t.ok(namespace.get('transaction'), "transaction should (yep) still be visible");
callback();
}, 50);
}
function outer() {
t.ok(namespace.get('transaction'), "transaction should be visible");
outerHandle = setInterval(function () {
clearInterval(outerHandle);
t.ok(namespace.get('transaction'), "transaction should still be visible");
inner(function () {
t.ok(namespace.get('transaction'), "transaction should even still be visible");
});
}, 50);
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('j', outer);
});
t.test("j. cleanup", destroy('j'));
t.test("k. async transaction with an async call with process.nextTick", function (t) {
t.plan(5);
var namespace = fresh('k', this);
function inner(callback) {
process.nextTick(function () {
t.ok(namespace.get('transaction'), "transaction should (yep) still be visible");
callback();
});
}
function outer() {
t.ok(namespace.get('transaction'), "transaction should be visible");
process.nextTick(function () {
t.ok(namespace.get('transaction'), "transaction should still be visible");
inner(function () {
t.ok(namespace.get('transaction'), "transaction should even still be visible");
});
});
}
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('k', function () { process.nextTick(outer); });
});
t.test("k. cleanup", destroy('k'));
t.test("l. async transaction with an async call with EventEmitter.emit", function (t) {
t.plan(4);
var namespace = fresh('l', this)
, outer = new EventEmitter()
, inner = new EventEmitter()
;
inner.on('pong', function (callback) {
t.ok(namespace.get('transaction'), "transaction should still be visible");
callback();
});
function outerCallback() {
t.ok(namespace.get('transaction'), "transaction should even still be visible");
}
outer.on('ping', function () {
t.ok(namespace.get('transaction'), "transaction should be visible");
inner.emit('pong', outerCallback);
});
t.notOk(namespace.get('transaction'), "transaction should not yet be visible");
runInTransaction('l', outer.emit.bind(outer, 'ping'));
});
t.test("l. cleanup", destroy('l'));
});
+28
View File
@@ -0,0 +1,28 @@
'use strict';
var tap = require('tap')
, test = tap.test
, createNamespace = require('../context.js').createNamespace
;
var zlib = require('zlib');
test("continuation-local state with zlib", function (t) {
t.plan(1);
var namespace = createNamespace('namespace');
namespace.run(function () {
namespace.set('test', 0xabad1dea);
t.test("deflate", function (t) {
namespace.run(function () {
namespace.set('test', 42);
zlib.deflate(new Buffer("Goodbye World"), function (err) {
if (err) throw err;
t.equal(namespace.get('test'), 42, "mutated state was preserved");
t.end();
});
});
});
});
});