opentelemetry-js/packages/opentelemetry-context-async.../test/AsyncHooksContextManager.te...

508 lines
18 KiB
TypeScript

/*
* Copyright The OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as assert from 'assert';
import {
AsyncHooksContextManager,
AsyncLocalStorageContextManager,
} from '../src';
import { EventEmitter } from 'events';
import { createContextKey, ROOT_CONTEXT } from '@opentelemetry/api';
for (const contextManagerClass of [
AsyncHooksContextManager,
AsyncLocalStorageContextManager,
]) {
describe(contextManagerClass.name, () => {
let contextManager:
| AsyncHooksContextManager
| AsyncLocalStorageContextManager;
const key1 = createContextKey('test key 1');
let otherContextManager:
| AsyncHooksContextManager
| AsyncLocalStorageContextManager;
beforeEach(() => {
contextManager = new contextManagerClass();
contextManager.enable();
});
afterEach(() => {
contextManager.disable();
otherContextManager?.disable();
});
describe('.enable()', () => {
it('should work', () => {
assert.doesNotThrow(() => {
contextManager = new contextManagerClass();
assert.ok(
contextManager.enable() === contextManager,
'should return this'
);
});
});
});
describe('.disable()', () => {
it('should work', () => {
assert.doesNotThrow(() => {
assert.ok(
contextManager.disable() === contextManager,
'should return this'
);
});
contextManager.enable();
});
});
describe('.with()', () => {
it('should run the callback (null as target)', done => {
contextManager.with(ROOT_CONTEXT, done);
});
it('should run the callback (object as target)', done => {
const test = ROOT_CONTEXT.setValue(key1, 1);
contextManager.with(test, () => {
assert.strictEqual(
contextManager.active(),
test,
'should have context'
);
return done();
});
});
it('should run the callback (when disabled)', done => {
contextManager.disable();
contextManager.with(ROOT_CONTEXT, () => {
contextManager.enable();
return done();
});
});
it('should rethrow errors', done => {
assert.throws(() => {
contextManager.with(ROOT_CONTEXT, () => {
throw new Error('This should be rethrown');
});
});
return done();
});
it('should forward this, arguments and return value', () => {
function fnWithThis(this: string, a: string, b: number): string {
assert.strictEqual(this, 'that');
assert.strictEqual(arguments.length, 2);
assert.strictEqual(a, 'one');
assert.strictEqual(b, 2);
return 'done';
}
const res = contextManager.with(
ROOT_CONTEXT,
fnWithThis,
'that',
'one',
2
);
assert.strictEqual(res, 'done');
assert.strictEqual(
contextManager.with(ROOT_CONTEXT, () => 3.14),
3.14
);
});
it('should finally restore an old context', done => {
const ctx1 = ROOT_CONTEXT.setValue(key1, 'ctx1');
const ctx2 = ROOT_CONTEXT.setValue(key1, 'ctx2');
contextManager.with(ctx1, () => {
assert.strictEqual(contextManager.active(), ctx1);
contextManager.with(ctx2, () => {
assert.strictEqual(contextManager.active(), ctx2);
});
assert.strictEqual(contextManager.active(), ctx1);
return done();
});
});
it('should finally restore an old context', done => {
const ctx1 = ROOT_CONTEXT.setValue(key1, 'ctx1');
contextManager.with(ctx1, () => {
assert.strictEqual(contextManager.active(), ctx1);
setTimeout(() => {
assert.strictEqual(contextManager.active(), ctx1);
return done();
});
});
});
it('async function called from nested "with" sync function should return nested context', done => {
const scope1 = '1' as any;
const scope2 = '2' as any;
const asyncFuncCalledDownstreamFromSync = async () => {
await (async () => {})();
assert.strictEqual(contextManager.active(), scope2);
return done();
};
contextManager.with(scope1, () => {
assert.strictEqual(contextManager.active(), scope1);
contextManager.with(scope2, () =>
asyncFuncCalledDownstreamFromSync()
);
assert.strictEqual(contextManager.active(), scope1);
});
assert.strictEqual(contextManager.active(), ROOT_CONTEXT);
});
it('should not loose the context', done => {
const scope1 = '1' as any;
contextManager.with(scope1, async () => {
assert.strictEqual(contextManager.active(), scope1);
await new Promise(resolve => setTimeout(resolve, 100));
assert.strictEqual(contextManager.active(), scope1);
return done();
});
assert.strictEqual(contextManager.active(), ROOT_CONTEXT);
});
it('should correctly restore context using async/await', async () => {
const scope1 = '1' as any;
const scope2 = '2' as any;
const scope3 = '3' as any;
const scope4 = '4' as any;
await contextManager.with(scope1, async () => {
assert.strictEqual(contextManager.active(), scope1);
await contextManager.with(scope2, async () => {
assert.strictEqual(contextManager.active(), scope2);
await contextManager.with(scope3, async () => {
assert.strictEqual(contextManager.active(), scope3);
await contextManager.with(scope4, async () => {
assert.strictEqual(contextManager.active(), scope4);
});
assert.strictEqual(contextManager.active(), scope3);
});
assert.strictEqual(contextManager.active(), scope2);
});
assert.strictEqual(contextManager.active(), scope1);
});
assert.strictEqual(contextManager.active(), ROOT_CONTEXT);
});
it('should works with multiple concurrent operations', done => {
const scope1 = '1' as any;
const scope2 = '2' as any;
const scope3 = '3' as any;
const scope4 = '4' as any;
let scope4Called = false;
contextManager.with(scope1, async () => {
assert.strictEqual(contextManager.active(), scope1);
setTimeout(async () => {
await contextManager.with(scope3, async () => {
assert.strictEqual(contextManager.active(), scope3);
});
assert.strictEqual(contextManager.active(), scope1);
assert.strictEqual(scope4Called, true);
return done();
}, 100);
assert.strictEqual(contextManager.active(), scope1);
});
assert.strictEqual(contextManager.active(), ROOT_CONTEXT);
contextManager.with(scope2, async () => {
assert.strictEqual(contextManager.active(), scope2);
setTimeout(() => {
contextManager.with(scope4, async () => {
assert.strictEqual(contextManager.active(), scope4);
scope4Called = true;
});
assert.strictEqual(contextManager.active(), scope2);
}, 20);
assert.strictEqual(contextManager.active(), scope2);
});
assert.strictEqual(contextManager.active(), ROOT_CONTEXT);
});
it('should work with timers using the same timeout', done => {
let cnt = 3;
function countDown() {
cnt--;
if (cnt === 0) done();
if (cnt < 0) throw new Error('too many calls to countDown()');
}
const time1 = 2;
const time2 = time1 + 1;
const rootCtx = contextManager.active();
const innerCtx = rootCtx.setValue(Symbol('test'), 23);
contextManager.with(innerCtx, () => {
setTimeout(() => {
assert.strictEqual(contextManager.active(), innerCtx);
countDown();
}, time1);
});
setTimeout(() => {
assert.strictEqual(contextManager.active(), rootCtx);
countDown();
}, time1);
setTimeout(() => {
assert.strictEqual(contextManager.active(), rootCtx);
countDown();
}, time2);
});
it('should not influence other instances', () => {
otherContextManager = new contextManagerClass();
otherContextManager.enable();
const context = ROOT_CONTEXT.setValue(key1, 2);
const otherContext = ROOT_CONTEXT.setValue(key1, 3);
contextManager.with(context, () => {
assert.strictEqual(contextManager.active(), context);
assert.strictEqual(otherContextManager.active(), ROOT_CONTEXT);
otherContextManager.with(otherContext, () => {
assert.strictEqual(contextManager.active(), context);
assert.strictEqual(otherContextManager.active(), otherContext);
});
});
});
});
describe('.bind(function)', () => {
it('should return the same target (when enabled)', () => {
const test = { a: 1 };
assert.deepStrictEqual(contextManager.bind(ROOT_CONTEXT, test), test);
});
it('should return the same target (when disabled)', () => {
contextManager.disable();
const test = { a: 1 };
assert.deepStrictEqual(contextManager.bind(ROOT_CONTEXT, test), test);
contextManager.enable();
});
it('should return current context (when enabled)', done => {
const context = ROOT_CONTEXT.setValue(key1, 1);
const fn = contextManager.bind(context, () => {
assert.strictEqual(
contextManager.active(),
context,
'should have context'
);
return done();
});
fn();
});
/**
* Even if asynchooks is disabled, the context propagation will
* still works but it might be lost after any async op.
*/
it('should return current context (when disabled)', done => {
contextManager.disable();
const context = ROOT_CONTEXT.setValue(key1, 1);
const fn = contextManager.bind(context, () => {
assert.strictEqual(
contextManager.active(),
context,
'should have context'
);
return done();
});
fn();
});
it('should fail to return current context with async op', done => {
const context = ROOT_CONTEXT.setValue(key1, 1);
const fn = contextManager.bind(context, () => {
assert.strictEqual(contextManager.active(), context);
setTimeout(() => {
assert.strictEqual(
contextManager.active(),
context,
'should have no context'
);
return done();
}, 100);
});
fn();
});
it('should not influence other instances', () => {
otherContextManager = new contextManagerClass();
otherContextManager.enable();
const context = ROOT_CONTEXT.setValue(key1, 2);
const otherContext = ROOT_CONTEXT.setValue(key1, 3);
const fn = otherContextManager.bind(
otherContext,
contextManager.bind(context, () => {
assert.strictEqual(contextManager.active(), context);
assert.strictEqual(otherContextManager.active(), otherContext);
})
);
fn();
});
});
describe('.bind(event-emitter)', () => {
it('should return the same target (when enabled)', () => {
const ee = new EventEmitter();
assert.deepStrictEqual(contextManager.bind(ROOT_CONTEXT, ee), ee);
});
it('should return the same target (when disabled)', () => {
const ee = new EventEmitter();
contextManager.disable();
assert.deepStrictEqual(contextManager.bind(ROOT_CONTEXT, ee), ee);
});
it('should return current context and removeListener (when enabled)', done => {
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
const handler = () => {
assert.deepStrictEqual(contextManager.active(), context);
patchedEE.removeListener('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 0);
return done();
};
patchedEE.on('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
patchedEE.emit('test');
});
it('should return current context and removeAllListener (when enabled)', done => {
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
const handler = () => {
assert.deepStrictEqual(contextManager.active(), context);
patchedEE.removeAllListeners('test');
assert.strictEqual(patchedEE.listeners('test').length, 0);
return done();
};
patchedEE.on('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
patchedEE.emit('test');
});
it('should remove event handler enabled by .once using removeListener (when enabled)', () => {
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
function handler() {}
patchedEE.once('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
patchedEE.removeListener('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 0);
});
it('should remove event handler enabled by .once using off (when enabled)', () => {
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
const handler = () => {};
patchedEE.once('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
patchedEE.off('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 0);
});
it('should return current context and removeAllListeners (when enabled)', done => {
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
const handler = () => {
assert.deepStrictEqual(contextManager.active(), context);
patchedEE.removeAllListeners();
assert.strictEqual(patchedEE.listeners('test').length, 0);
assert.strictEqual(patchedEE.listeners('test1').length, 0);
return done();
};
patchedEE.on('test', handler);
patchedEE.on('test1', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
assert.strictEqual(patchedEE.listeners('test1').length, 1);
patchedEE.emit('test');
});
/**
* Even if asynchooks is disabled, the context propagation will
* still works but it might be lost after any async op.
*/
it('should return context (when disabled)', done => {
contextManager.disable();
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
const handler = () => {
assert.deepStrictEqual(contextManager.active(), context);
patchedEE.removeListener('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 0);
return done();
};
patchedEE.on('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
patchedEE.emit('test');
});
it('should not return current context with async op', done => {
const ee = new EventEmitter();
const context = ROOT_CONTEXT.setValue(key1, 1);
const patchedEE = contextManager.bind(context, ee);
const handler = () => {
assert.deepStrictEqual(contextManager.active(), context);
setImmediate(() => {
assert.deepStrictEqual(contextManager.active(), context);
patchedEE.removeAllListeners('test');
assert.strictEqual(patchedEE.listeners('test').length, 0);
return done();
});
};
patchedEE.on('test', handler);
assert.strictEqual(patchedEE.listeners('test').length, 1);
patchedEE.emit('test');
});
it('should not influence other instances', () => {
const ee = new EventEmitter();
otherContextManager = new contextManagerClass();
otherContextManager.enable();
const context = ROOT_CONTEXT.setValue(key1, 2);
const otherContext = ROOT_CONTEXT.setValue(key1, 3);
const patchedEE = otherContextManager.bind(
otherContext,
contextManager.bind(context, ee)
);
const handler = () => {
assert.strictEqual(contextManager.active(), context);
assert.strictEqual(otherContextManager.active(), otherContext);
};
patchedEE.on('test', handler);
patchedEE.emit('test');
});
});
});
}