Update test framework: fix run_tests.py to support all test files, add auto-import-check for test files

This commit is contained in:
qiaoxinjiu
2026-05-09 15:11:30 +08:00
parent eb053a347f
commit eaba8328da
21739 changed files with 2236758 additions and 719 deletions

View File

@@ -0,0 +1,36 @@
import type { Hooked } from './RequireInTheMiddleSingleton';
export declare const ModuleNameSeparator = "/";
type ModuleNameTrieSearchOptions = {
/**
* Whether to return the results in insertion order
*/
maintainInsertionOrder?: boolean;
/**
* Whether to return only full matches
*/
fullOnly?: boolean;
};
/**
* Trie containing nodes that represent a part of a module name (i.e. the parts separated by forward slash)
*/
export declare class ModuleNameTrie {
private _trie;
private _counter;
/**
* Insert a module hook into the trie
*
* @param {Hooked} hook Hook
*/
insert(hook: Hooked): void;
/**
* Search for matching hooks in the trie
*
* @param {string} moduleName Module name
* @param {boolean} maintainInsertionOrder Whether to return the results in insertion order
* @param {boolean} fullOnly Whether to return only full matches
* @returns {Hooked[]} Matching hooks
*/
search(moduleName: string, { maintainInsertionOrder, fullOnly }?: ModuleNameTrieSearchOptions): Hooked[];
}
export {};
//# sourceMappingURL=ModuleNameTrie.d.ts.map

View File

@@ -0,0 +1,85 @@
/*
* 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.
*/
export const ModuleNameSeparator = '/';
/**
* Node in a `ModuleNameTrie`
*/
class ModuleNameTrieNode {
hooks = [];
children = new Map();
}
/**
* Trie containing nodes that represent a part of a module name (i.e. the parts separated by forward slash)
*/
export class ModuleNameTrie {
_trie = new ModuleNameTrieNode();
_counter = 0;
/**
* Insert a module hook into the trie
*
* @param {Hooked} hook Hook
*/
insert(hook) {
let trieNode = this._trie;
for (const moduleNamePart of hook.moduleName.split(ModuleNameSeparator)) {
let nextNode = trieNode.children.get(moduleNamePart);
if (!nextNode) {
nextNode = new ModuleNameTrieNode();
trieNode.children.set(moduleNamePart, nextNode);
}
trieNode = nextNode;
}
trieNode.hooks.push({ hook, insertedId: this._counter++ });
}
/**
* Search for matching hooks in the trie
*
* @param {string} moduleName Module name
* @param {boolean} maintainInsertionOrder Whether to return the results in insertion order
* @param {boolean} fullOnly Whether to return only full matches
* @returns {Hooked[]} Matching hooks
*/
search(moduleName, { maintainInsertionOrder, fullOnly } = {}) {
let trieNode = this._trie;
const results = [];
let foundFull = true;
for (const moduleNamePart of moduleName.split(ModuleNameSeparator)) {
const nextNode = trieNode.children.get(moduleNamePart);
if (!nextNode) {
foundFull = false;
break;
}
if (!fullOnly) {
results.push(...nextNode.hooks);
}
trieNode = nextNode;
}
if (fullOnly && foundFull) {
results.push(...trieNode.hooks);
}
if (results.length === 0) {
return [];
}
if (results.length === 1) {
return [results[0].hook];
}
if (maintainInsertionOrder) {
results.sort((a, b) => a.insertedId - b.insertedId);
}
return results.map(({ hook }) => hook);
}
}
//# sourceMappingURL=ModuleNameTrie.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,35 @@
import type { OnRequireFn } from 'require-in-the-middle';
export type Hooked = {
moduleName: string;
onRequire: OnRequireFn;
};
/**
* Singleton class for `require-in-the-middle`
* Allows instrumentation plugins to patch modules with only a single `require` patch
* WARNING: Because this class will create its own `require-in-the-middle` (RITM) instance,
* we should minimize the number of new instances of this class.
* Multiple instances of `@opentelemetry/instrumentation` (e.g. multiple versions) in a single process
* will result in multiple instances of RITM, which will have an impact
* on the performance of instrumentation hooks being applied.
*/
export declare class RequireInTheMiddleSingleton {
private _moduleNameTrie;
private static _instance?;
private constructor();
private _initialize;
/**
* Register a hook with `require-in-the-middle`
*
* @param {string} moduleName Module name
* @param {OnRequireFn} onRequire Hook function
* @returns {Hooked} Registered hook
*/
register(moduleName: string, onRequire: OnRequireFn): Hooked;
/**
* Get the `RequireInTheMiddleSingleton` singleton
*
* @returns {RequireInTheMiddleSingleton} Singleton of `RequireInTheMiddleSingleton`
*/
static getInstance(): RequireInTheMiddleSingleton;
}
//# sourceMappingURL=RequireInTheMiddleSingleton.d.ts.map

View File

@@ -0,0 +1,107 @@
/*
* 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 { Hook } from 'require-in-the-middle';
import * as path from 'path';
import { ModuleNameTrie, ModuleNameSeparator } from './ModuleNameTrie';
/**
* Whether Mocha is running in this process
* Inspired by https://github.com/AndreasPizsa/detect-mocha
*
* @type {boolean}
*/
const isMocha = [
'afterEach',
'after',
'beforeEach',
'before',
'describe',
'it',
].every(fn => {
// @ts-expect-error TS7053: Element implicitly has an 'any' type
return typeof global[fn] === 'function';
});
/**
* Singleton class for `require-in-the-middle`
* Allows instrumentation plugins to patch modules with only a single `require` patch
* WARNING: Because this class will create its own `require-in-the-middle` (RITM) instance,
* we should minimize the number of new instances of this class.
* Multiple instances of `@opentelemetry/instrumentation` (e.g. multiple versions) in a single process
* will result in multiple instances of RITM, which will have an impact
* on the performance of instrumentation hooks being applied.
*/
export class RequireInTheMiddleSingleton {
_moduleNameTrie = new ModuleNameTrie();
static _instance;
constructor() {
this._initialize();
}
_initialize() {
new Hook(
// Intercept all `require` calls; we will filter the matching ones below
null, { internals: true }, (exports, name, basedir) => {
// For internal files on Windows, `name` will use backslash as the path separator
const normalizedModuleName = normalizePathSeparators(name);
const matches = this._moduleNameTrie.search(normalizedModuleName, {
maintainInsertionOrder: true,
// For core modules (e.g. `fs`), do not match on sub-paths (e.g. `fs/promises').
// This matches the behavior of `require-in-the-middle`.
// `basedir` is always `undefined` for core modules.
fullOnly: basedir === undefined,
});
for (const { onRequire } of matches) {
exports = onRequire(exports, name, basedir);
}
return exports;
});
}
/**
* Register a hook with `require-in-the-middle`
*
* @param {string} moduleName Module name
* @param {OnRequireFn} onRequire Hook function
* @returns {Hooked} Registered hook
*/
register(moduleName, onRequire) {
const hooked = { moduleName, onRequire };
this._moduleNameTrie.insert(hooked);
return hooked;
}
/**
* Get the `RequireInTheMiddleSingleton` singleton
*
* @returns {RequireInTheMiddleSingleton} Singleton of `RequireInTheMiddleSingleton`
*/
static getInstance() {
// Mocha runs all test suites in the same process
// This prevents test suites from sharing a singleton
if (isMocha)
return new RequireInTheMiddleSingleton();
return (this._instance =
this._instance ?? new RequireInTheMiddleSingleton());
}
}
/**
* Normalize the path separators to forward slash in a module name or path
*
* @param {string} moduleNameOrPath Module name or path
* @returns {string} Normalized module name or path
*/
function normalizePathSeparators(moduleNameOrPath) {
return path.sep !== ModuleNameSeparator
? moduleNameOrPath.split(path.sep).join(ModuleNameSeparator)
: moduleNameOrPath;
}
//# sourceMappingURL=RequireInTheMiddleSingleton.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export { InstrumentationBase } from './instrumentation';
export { normalize } from './normalize';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1,18 @@
/*
* 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.
*/
export { InstrumentationBase } from './instrumentation';
export { normalize } from './normalize';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/platform/node/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nexport { InstrumentationBase } from './instrumentation';\nexport { normalize } from './normalize';\n"]}

View File

@@ -0,0 +1,25 @@
import * as types from '../../types';
import { wrap, unwrap, massWrap, massUnwrap } from 'shimmer';
import { InstrumentationAbstract } from '../../instrumentation';
import { InstrumentationConfig } from '../../types';
/**
* Base abstract class for instrumenting node plugins
*/
export declare abstract class InstrumentationBase<ConfigType extends InstrumentationConfig = InstrumentationConfig> extends InstrumentationAbstract<ConfigType> implements types.Instrumentation<ConfigType> {
private _modules;
private _hooks;
private _requireInTheMiddleSingleton;
private _enabled;
constructor(instrumentationName: string, instrumentationVersion: string, config: ConfigType);
protected _wrap: typeof wrap;
protected _unwrap: typeof unwrap;
protected _massWrap: typeof massWrap;
protected _massUnwrap: typeof massUnwrap;
private _warnOnPreloadedModules;
private _extractPackageVersion;
private _onRequire;
enable(): void;
disable(): void;
isEnabled(): boolean;
}
//# sourceMappingURL=instrumentation.d.ts.map

View File

@@ -0,0 +1,278 @@
/*
* 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 path from 'path';
import { types as utilTypes } from 'util';
import { satisfies } from '../../semver';
import { wrap, unwrap } from 'shimmer';
import { InstrumentationAbstract } from '../../instrumentation';
import { RequireInTheMiddleSingleton, } from './RequireInTheMiddleSingleton';
import { Hook as HookImport } from 'import-in-the-middle';
import { diag } from '@opentelemetry/api';
import { Hook as HookRequire } from 'require-in-the-middle';
import { readFileSync } from 'fs';
import { isWrapped } from '../../utils';
/**
* Base abstract class for instrumenting node plugins
*/
export class InstrumentationBase extends InstrumentationAbstract {
_modules;
_hooks = [];
_requireInTheMiddleSingleton = RequireInTheMiddleSingleton.getInstance();
_enabled = false;
constructor(instrumentationName, instrumentationVersion, config) {
super(instrumentationName, instrumentationVersion, config);
let modules = this.init();
if (modules && !Array.isArray(modules)) {
modules = [modules];
}
this._modules = modules || [];
if (this._config.enabled) {
this.enable();
}
}
_wrap = (moduleExports, name, wrapper) => {
if (isWrapped(moduleExports[name])) {
this._unwrap(moduleExports, name);
}
if (!utilTypes.isProxy(moduleExports)) {
return wrap(moduleExports, name, wrapper);
}
else {
const wrapped = wrap(Object.assign({}, moduleExports), name, wrapper);
Object.defineProperty(moduleExports, name, {
value: wrapped,
});
return wrapped;
}
};
_unwrap = (moduleExports, name) => {
if (!utilTypes.isProxy(moduleExports)) {
return unwrap(moduleExports, name);
}
else {
return Object.defineProperty(moduleExports, name, {
value: moduleExports[name],
});
}
};
_massWrap = (moduleExportsArray, names, wrapper) => {
if (!moduleExportsArray) {
diag.error('must provide one or more modules to patch');
return;
}
else if (!Array.isArray(moduleExportsArray)) {
moduleExportsArray = [moduleExportsArray];
}
if (!(names && Array.isArray(names))) {
diag.error('must provide one or more functions to wrap on modules');
return;
}
moduleExportsArray.forEach(moduleExports => {
names.forEach(name => {
this._wrap(moduleExports, name, wrapper);
});
});
};
_massUnwrap = (moduleExportsArray, names) => {
if (!moduleExportsArray) {
diag.error('must provide one or more modules to patch');
return;
}
else if (!Array.isArray(moduleExportsArray)) {
moduleExportsArray = [moduleExportsArray];
}
if (!(names && Array.isArray(names))) {
diag.error('must provide one or more functions to wrap on modules');
return;
}
moduleExportsArray.forEach(moduleExports => {
names.forEach(name => {
this._unwrap(moduleExports, name);
});
});
};
_warnOnPreloadedModules() {
this._modules.forEach((module) => {
const { name } = module;
try {
const resolvedModule = require.resolve(name);
if (require.cache[resolvedModule]) {
// Module is already cached, which means the instrumentation hook might not work
this._diag.warn(`Module ${name} has been loaded before ${this.instrumentationName} so it might not work, please initialize it before requiring ${name}`);
}
}
catch {
// Module isn't available, we can simply skip
}
});
}
_extractPackageVersion(baseDir) {
try {
const json = readFileSync(path.join(baseDir, 'package.json'), {
encoding: 'utf8',
});
const version = JSON.parse(json).version;
return typeof version === 'string' ? version : undefined;
}
catch (error) {
diag.warn('Failed extracting version', baseDir);
}
return undefined;
}
_onRequire(module, exports, name, baseDir) {
if (!baseDir) {
if (typeof module.patch === 'function') {
module.moduleExports = exports;
if (this._enabled) {
this._diag.debug('Applying instrumentation patch for nodejs core module on require hook', {
module: module.name,
});
return module.patch(exports);
}
}
return exports;
}
const version = this._extractPackageVersion(baseDir);
module.moduleVersion = version;
if (module.name === name) {
// main module
if (isSupported(module.supportedVersions, version, module.includePrerelease)) {
if (typeof module.patch === 'function') {
module.moduleExports = exports;
if (this._enabled) {
this._diag.debug('Applying instrumentation patch for module on require hook', {
module: module.name,
version: module.moduleVersion,
baseDir,
});
return module.patch(exports, module.moduleVersion);
}
}
}
return exports;
}
// internal file
const files = module.files ?? [];
const normalizedName = path.normalize(name);
const supportedFileInstrumentations = files
.filter(f => f.name === normalizedName)
.filter(f => isSupported(f.supportedVersions, version, module.includePrerelease));
return supportedFileInstrumentations.reduce((patchedExports, file) => {
file.moduleExports = patchedExports;
if (this._enabled) {
this._diag.debug('Applying instrumentation patch for nodejs module file on require hook', {
module: module.name,
version: module.moduleVersion,
fileName: file.name,
baseDir,
});
// patch signature is not typed, so we cast it assuming it's correct
return file.patch(patchedExports, module.moduleVersion);
}
return patchedExports;
}, exports);
}
enable() {
if (this._enabled) {
return;
}
this._enabled = true;
// already hooked, just call patch again
if (this._hooks.length > 0) {
for (const module of this._modules) {
if (typeof module.patch === 'function' && module.moduleExports) {
this._diag.debug('Applying instrumentation patch for nodejs module on instrumentation enabled', {
module: module.name,
version: module.moduleVersion,
});
module.patch(module.moduleExports, module.moduleVersion);
}
for (const file of module.files) {
if (file.moduleExports) {
this._diag.debug('Applying instrumentation patch for nodejs module file on instrumentation enabled', {
module: module.name,
version: module.moduleVersion,
fileName: file.name,
});
file.patch(file.moduleExports, module.moduleVersion);
}
}
}
return;
}
this._warnOnPreloadedModules();
for (const module of this._modules) {
const hookFn = (exports, name, baseDir) => {
if (!baseDir && path.isAbsolute(name)) {
const parsedPath = path.parse(name);
name = parsedPath.name;
baseDir = parsedPath.dir;
}
return this._onRequire(module, exports, name, baseDir);
};
const onRequire = (exports, name, baseDir) => {
return this._onRequire(module, exports, name, baseDir);
};
// `RequireInTheMiddleSingleton` does not support absolute paths.
// For an absolute paths, we must create a separate instance of the
// require-in-the-middle `Hook`.
const hook = path.isAbsolute(module.name)
? new HookRequire([module.name], { internals: true }, onRequire)
: this._requireInTheMiddleSingleton.register(module.name, onRequire);
this._hooks.push(hook);
const esmHook = new HookImport([module.name], { internals: false }, hookFn);
this._hooks.push(esmHook);
}
}
disable() {
if (!this._enabled) {
return;
}
this._enabled = false;
for (const module of this._modules) {
if (typeof module.unpatch === 'function' && module.moduleExports) {
this._diag.debug('Removing instrumentation patch for nodejs module on instrumentation disabled', {
module: module.name,
version: module.moduleVersion,
});
module.unpatch(module.moduleExports, module.moduleVersion);
}
for (const file of module.files) {
if (file.moduleExports) {
this._diag.debug('Removing instrumentation patch for nodejs module file on instrumentation disabled', {
module: module.name,
version: module.moduleVersion,
fileName: file.name,
});
file.unpatch(file.moduleExports, module.moduleVersion);
}
}
}
}
isEnabled() {
return this._enabled;
}
}
function isSupported(supportedVersions, version, includePrerelease) {
if (typeof version === 'undefined') {
// If we don't have the version, accept the wildcard case only
return supportedVersions.includes('*');
}
return supportedVersions.some(supportedVersion => {
return satisfies(version, supportedVersion, { includePrerelease });
});
}
//# sourceMappingURL=instrumentation.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2 @@
export { normalize } from 'path';
//# sourceMappingURL=normalize.d.ts.map

View File

@@ -0,0 +1,17 @@
/*
* 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.
*/
export { normalize } from 'path';
//# sourceMappingURL=normalize.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../../src/platform/node/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC","sourcesContent":["/*\n * Copyright The OpenTelemetry Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nexport { normalize } from 'path';\n"]}