opentelemetry-js/packages/opentelemetry-node/test/instrumentation/PluginLoader.test.ts

240 lines
8.5 KiB
TypeScript

/*!
* Copyright 2019, 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 { NoopLogger, NoopTracerRegistry } from '@opentelemetry/core';
import * as assert from 'assert';
import * as path from 'path';
import {
HookState,
PluginLoader,
Plugins,
searchPathForTest,
} from '../../src/instrumentation/PluginLoader';
const INSTALLED_PLUGINS_PATH = path.join(__dirname, 'node_modules');
const simplePlugins: Plugins = {
'simple-module': {
enabled: true,
path: '@opentelemetry/plugin-simple-module',
ignoreMethods: [],
ignoreUrls: [],
},
};
const httpPlugins: Plugins = {
http: {
enabled: true,
path: '@opentelemetry/plugin-http-module',
ignoreMethods: [],
ignoreUrls: [],
},
};
const disablePlugins: Plugins = {
'simple-module': {
enabled: false,
path: '@opentelemetry/plugin-simple-module',
},
nonexistent: {
enabled: false,
path: '@opentelemetry/plugin-nonexistent-module',
},
};
const nonexistentPlugins: Plugins = {
nonexistent: {
enabled: true,
path: '@opentelemetry/plugin-nonexistent-module',
},
};
const missingPathPlugins: Plugins = {
'simple-module': {
enabled: true,
},
nonexistent: {
enabled: true,
},
};
const supportedVersionPlugins: Plugins = {
'supported-module': {
enabled: true,
path: '@opentelemetry/plugin-supported-module',
},
};
const notSupportedVersionPlugins: Plugins = {
'notsupported-module': {
enabled: true,
path: 'notsupported-module',
},
};
describe('PluginLoader', () => {
const registry = new NoopTracerRegistry();
const logger = new NoopLogger();
before(() => {
module.paths.push(INSTALLED_PLUGINS_PATH);
searchPathForTest(INSTALLED_PLUGINS_PATH);
});
afterEach(() => {
// clear require cache
Object.keys(require.cache).forEach(key => delete require.cache[key]);
});
describe('.state()', () => {
it('returns UNINITIALIZED when first called', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_hookState'], HookState.UNINITIALIZED);
});
it('transitions from UNINITIALIZED to ENABLED', () => {
const pluginLoader = new PluginLoader(registry, logger);
pluginLoader.load(simplePlugins);
assert.strictEqual(pluginLoader['_hookState'], HookState.ENABLED);
pluginLoader.unload();
});
it('transitions from ENABLED to DISABLED', () => {
const pluginLoader = new PluginLoader(registry, logger);
pluginLoader.load(simplePlugins).unload();
assert.strictEqual(pluginLoader['_hookState'], HookState.DISABLED);
});
});
describe('.load()', () => {
it('sanity check', () => {
// Ensure that module fixtures contain values that we expect.
const simpleModule = require('simple-module');
const simpleModule001 = require('supported-module');
const simpleModule100 = require('notsupported-module');
assert.strictEqual(simpleModule.name(), 'simple-module');
assert.strictEqual(simpleModule001.name(), 'supported-module');
assert.strictEqual(simpleModule100.name(), 'notsupported-module');
assert.strictEqual(simpleModule.value(), 0);
assert.strictEqual(simpleModule001.value(), 0);
assert.strictEqual(simpleModule100.value(), 0);
assert.throws(() => require('nonexistent-module'));
});
it('should load a plugin and patch the target modules', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(simplePlugins);
// The hook is only called the first time the module is loaded.
const simpleModule = require('simple-module');
assert.strictEqual(pluginLoader['_plugins'].length, 1);
assert.strictEqual(simpleModule.value(), 1);
assert.strictEqual(simpleModule.name(), 'patched-simple-module');
pluginLoader.unload();
});
it('should load a plugin and patch the core module', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(httpPlugins);
// The hook is only called the first time the module is loaded.
const httpModule = require('http');
assert.strictEqual(pluginLoader['_plugins'].length, 1);
assert.strictEqual(httpModule.get(), 'patched');
pluginLoader.unload();
});
// @TODO: simplify this test once we can load module with custom path
it('should not load the plugin when supported versions does not match', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(notSupportedVersionPlugins);
// The hook is only called the first time the module is loaded.
require('notsupported-module');
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.unload();
});
// @TODO: simplify this test once we can load module with custom path
it('should load a plugin and patch the target modules when supported versions match', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(supportedVersionPlugins);
// The hook is only called the first time the module is loaded.
const simpleModule = require('supported-module');
assert.strictEqual(pluginLoader['_plugins'].length, 1);
assert.strictEqual(simpleModule.value(), 1);
assert.strictEqual(simpleModule.name(), 'patched-supported-module');
pluginLoader.unload();
});
it('should not load a plugin when value is false', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(disablePlugins);
const simpleModule = require('simple-module');
assert.strictEqual(pluginLoader['_plugins'].length, 0);
assert.strictEqual(simpleModule.value(), 0);
assert.strictEqual(simpleModule.name(), 'simple-module');
pluginLoader.unload();
});
it('should not load a plugin when value is true but path is missing', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(missingPathPlugins);
const simpleModule = require('simple-module');
assert.strictEqual(pluginLoader['_plugins'].length, 0);
assert.strictEqual(simpleModule.value(), 0);
assert.strictEqual(simpleModule.name(), 'simple-module');
pluginLoader.unload();
});
it('should not load a non existing plugin', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(nonexistentPlugins);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.unload();
});
it(`doesn't patch modules for which plugins aren't specified`, () => {
const pluginLoader = new PluginLoader(registry, logger);
pluginLoader.load({});
assert.strictEqual(require('simple-module').value(), 0);
pluginLoader.unload();
});
});
describe('.unload()', () => {
it('should unload the plugins and unpatch the target module when unloads', () => {
const pluginLoader = new PluginLoader(registry, logger);
assert.strictEqual(pluginLoader['_plugins'].length, 0);
pluginLoader.load(simplePlugins);
// The hook is only called the first time the module is loaded.
const simpleModule = require('simple-module');
assert.strictEqual(pluginLoader['_plugins'].length, 1);
assert.strictEqual(simpleModule.value(), 1);
assert.strictEqual(simpleModule.name(), 'patched-simple-module');
pluginLoader.unload();
assert.strictEqual(pluginLoader['_plugins'].length, 0);
assert.strictEqual(simpleModule.name(), 'simple-module');
assert.strictEqual(simpleModule.value(), 0);
});
});
});