-
Notifications
You must be signed in to change notification settings - Fork 539
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(instrumentation-fs): fix instrumentation of fs/promises
#1375
Changes from all commits
90d9f0d
0641868
1effd28
181d844
09a3870
8168efe
2fa621d
2e6a40b
5601545
8af53f5
40d77e9
1080abc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
/* | ||
* 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 { context, trace } from '@opentelemetry/api'; | ||
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks'; | ||
import { | ||
BasicTracerProvider, | ||
InMemorySpanExporter, | ||
SimpleSpanProcessor, | ||
} from '@opentelemetry/sdk-trace-base'; | ||
import * as assert from 'assert'; | ||
import Instrumentation from '../src'; | ||
import * as sinon from 'sinon'; | ||
import type * as FSPromisesType from 'fs/promises'; | ||
import tests, { FsFunction, TestCase, TestCreator } from './definitions'; | ||
import type { FPMember, EndHook } from '../src/types'; | ||
import { assertSpans, makeRootSpanName } from './utils'; | ||
|
||
const supportsPromises = | ||
parseInt(process.versions.node.split('.')[0], 10) >= 14; | ||
Comment on lines
+31
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The repo supports node >= 14 anyway, so I guess this is not needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved by #1413 |
||
|
||
const TEST_ATTRIBUTE = 'test.attr'; | ||
const TEST_VALUE = 'test.attr.value'; | ||
|
||
const endHook = <EndHook>sinon.spy((fnName, { args, span }) => { | ||
span.setAttribute(TEST_ATTRIBUTE, TEST_VALUE); | ||
}); | ||
const pluginConfig = { | ||
endHook, | ||
}; | ||
const provider = new BasicTracerProvider(); | ||
const tracer = provider.getTracer('default'); | ||
const memoryExporter = new InMemorySpanExporter(); | ||
provider.addSpanProcessor(new SimpleSpanProcessor(memoryExporter)); | ||
|
||
if (supportsPromises) { | ||
describe('fs/promises instrumentation', () => { | ||
let contextManager: AsyncHooksContextManager; | ||
let fsPromises: typeof FSPromisesType; | ||
let plugin: Instrumentation; | ||
|
||
beforeEach(async () => { | ||
contextManager = new AsyncHooksContextManager(); | ||
context.setGlobalContextManager(contextManager.enable()); | ||
plugin = new Instrumentation(pluginConfig); | ||
plugin.setTracerProvider(provider); | ||
plugin.enable(); | ||
fsPromises = require('fs/promises'); | ||
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); | ||
}); | ||
|
||
afterEach(() => { | ||
plugin.disable(); | ||
memoryExporter.reset(); | ||
context.disable(); | ||
}); | ||
|
||
const promiseTest: TestCreator<FPMember> = ( | ||
name: FPMember, | ||
args, | ||
{ error, result, resultAsError = null, hasPromiseVersion = true }, | ||
spans | ||
) => { | ||
if (!hasPromiseVersion) return; | ||
const rootSpanName = makeRootSpanName(name); | ||
it(`promises.${name} ${error ? 'error' : 'success'}`, async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe I'm missing something, but isn't this test already exist in the file 'fs.test.ts' ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is testing |
||
const rootSpan = tracer.startSpan(rootSpanName); | ||
|
||
assert.strictEqual(memoryExporter.getFinishedSpans().length, 0); | ||
await context | ||
.with(trace.setSpan(context.active(), rootSpan), () => { | ||
// eslint-disable-next-line node/no-unsupported-features/node-builtins | ||
assert( | ||
typeof fsPromises[name] === 'function', | ||
`Expected fsPromises.${name} to be a function` | ||
); | ||
return Reflect.apply(fsPromises[name], fsPromises, args); | ||
}) | ||
.then((actualResult: any) => { | ||
if (error) { | ||
assert.fail(`promises.${name} did not reject`); | ||
} else { | ||
assert.deepEqual(actualResult, result ?? resultAsError); | ||
} | ||
}) | ||
.catch((actualError: any) => { | ||
assert( | ||
actualError instanceof Error, | ||
`Expected caugth error to be instance of Error. Got ${actualError}` | ||
); | ||
if (error) { | ||
assert( | ||
error.test(actualError?.message ?? ''), | ||
`Expected "${actualError?.message}" to match ${error}` | ||
); | ||
} else { | ||
actualError.message = `Did not expect promises.${name} to reject: ${actualError.message}`; | ||
assert.fail(actualError); | ||
} | ||
}); | ||
rootSpan.end(); | ||
assertSpans(memoryExporter.getFinishedSpans(), [ | ||
...spans.map((s: any) => { | ||
const spanName = s.name.replace(/%NAME/, name); | ||
const attributes = { | ||
...(s.attributes ?? {}), | ||
}; | ||
attributes[TEST_ATTRIBUTE] = TEST_VALUE; | ||
return { | ||
...s, | ||
name: spanName, | ||
attributes, | ||
}; | ||
}), | ||
{ name: rootSpanName }, | ||
]); | ||
}); | ||
}; | ||
|
||
const selection: TestCase[] = tests.filter( | ||
([name, , , , options = {}]) => | ||
options.promise !== false && name !== ('exists' as FsFunction) | ||
); | ||
|
||
describe('Instrumentation enabled', () => { | ||
selection.forEach(([name, args, result, spans]) => { | ||
promiseTest(name as FPMember, args, result, spans); | ||
}); | ||
}); | ||
|
||
describe('Instrumentation disabled', () => { | ||
beforeEach(() => { | ||
plugin.disable(); | ||
}); | ||
|
||
selection.forEach(([name, args, result]) => { | ||
promiseTest(name as FPMember, args, result, []); | ||
}); | ||
}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: i think this type is more explicit