UNPKG

hawkly

Version:
431 lines (372 loc) 12.9 kB
import * as opentracing from 'opentracing'; import * as sinon from 'sinon'; import { Tracer } from './Tracer'; import { test } from 'ava'; // This file contains tests for the public interface test('Check Tracer options are set correctly', async (t: any) => { const accessToken: string = 'testAccessToken'; const componentName: string = 'testComponentname'; const recordCallback: any = sinon.spy(); const tracer: any = new Tracer({ accessToken, componentName, recordCallback, }); t.is(tracer.accessToken, accessToken, 'accessToken does not match'); t.is(tracer.componentName, componentName, 'componentName does not match'); t.is(tracer.recordCallback, recordCallback, 'recordCallback does not match'); // Call the recordCallback tracer.recordCallback(); t.true(recordCallback.called); }); test('Check Tracer throws when constructor options are not set correctly', async (t: any) => { // Check accessToken errors const accessTokenUndefinedError: any = t.throws(() => { const tracer: Tracer = new Tracer({ componentName: 'help', }); tracer.clear(); }); t.is(accessTokenUndefinedError.message, 'You need to set your accessToken for the hawkly tracer'); const accessTokenNonStringError: any = t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 234, // a non string should throw componentName: 'im', }); tracer.clear(); }); t.is(accessTokenNonStringError.message, 'The accessToken must be a string'); // Check componentName errors const componentNameUndefinedError: any = t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'trapped', }); tracer.clear(); }); t.is(componentNameUndefinedError.message, 'You need to set a componentName to identify where these traces are coming from'); const componentNameNonStringError: any = t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'in', componentName: 23423, // a non string should throw }); tracer.clear(); }); t.is(componentNameNonStringError.message, 'The componentName must be a string'); // Check recordCallback errors const recordCallbackNonFunctionButDefinedError: any = t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'a', componentName: 'testing', recordCallback: 'factory', // anything defined that is not a function should throw }); tracer.clear(); }); t.is(recordCallbackNonFunctionButDefinedError.message, 'recordCallback must be a function'); }); // Havent been able to get this to work // test('should log error if the context is not a Span or Context', async (t: any) => { // const tracer: Tracer = new Tracer({ // accessToken: 'test', // componentName: 'test', // recordCallback: () => { // // // }, // }); // const span: any = tracer.startSpan('test2', { childOf: undefined }); // span.finish(); // // t.is(tracer.internalEvents // console.log(tracer.internalEvents); // // t.true(tracer.internalEvents.find((event: any) => { // // return event.msg === 'Span reference has an invalid context' // // && event.payload === 'parent'; // // })); // }); test('supports childOf with a Span object', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const parent: any = tracer.startSpan('test1'); const span: any = tracer.startSpan('test2', { childOf: parent }); span.finish(); parent.finish(); t.true(span.context().traceId === parent.context().traceId, 'traceId does not match'); t.true(span.context().parentId === parent.context().spanId, 'parentId does not match'); t.true(parent.context().referenceType === 'root'); t.true(span.context().referenceType === 'childOf'); }); test('supports childOf with a SpanContext object', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const parent: any = tracer.startSpan('test1'); const span: any = tracer.startSpan('test2', { childOf: parent.context() }); span.finish(); parent.finish(); t.true(span.context().traceId === parent.context().traceId, 'traceId does not match'); t.true(span.context().parentId === parent.context().spanId, 'parentId does not match'); t.true(parent.context().referenceType === 'root'); t.true(span.context().referenceType === 'childOf'); }); test('supports followsFrom with a Span object', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const parent: any = tracer.startSpan('test1'); const span: any = tracer.startSpan('test2', { followsFrom: parent }); span.finish(); parent.finish(); t.true(span.context().traceId === parent.context().traceId, 'traceId does not match'); t.true(span.context().parentId === parent.context().spanId, 'parentId does not match'); t.true(parent.context().referenceType === 'root'); t.true(span.context().referenceType === 'followsFrom'); }); test('supports followsFrom with a SpanContext object', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const parent: any = tracer.startSpan('test1'); const parentContext: any = parent.context(); parent.finish(); const span: any = tracer.startSpan('test2', { followsFrom: parentContext }); span.finish(); t.true(span.context().traceId === parent.context().traceId, 'traceId does not match'); t.true(span.context().parentId === parent.context().spanId, 'parentId does not match'); t.true(parent.context().referenceType === 'root'); t.true(span.context().referenceType === 'followsFrom'); }); test('supports startTime', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const now: number = Date.now() - 5000; const span: any = tracer.startSpan('test2', { startTime: now }); span.finish(); t.is(span._startMs, now, 'start time does not match what was supplied'); }); test('should throw when startTime is not a number', async (t: any) => { const error: any = await t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const span: any = tracer.startSpan('test2', { startTime: 'now' }); span.finish(); }); t.is(error.message, 'startTime must be a timestamp of type number'); }); test('supports tags', async (t: any) => { await t.notThrows(() => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); // Verify that we can add tags at startSpan time. const span: any = tracer.startSpan('test', { tags: { tag_a: 1, tag_b: 'b', tag_c: true, }, }); span.finish(); }); }); test('should throw when tags is not an object', async (t: any) => { const error: any = await t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const span: any = tracer.startSpan('test', { tags: 'tags', }); span.finish(); }); t.is(error.message, 'tags must be an object'); }); test('should throw when tags is an Array', async (t: any) => { const error: any = await t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const span: any = tracer.startSpan('test', { tags: [], }); span.finish(); }); t.is(error.message, 'tags must be an object'); }); test('should handle a large number of spans gracefully', async (t: any) => { await t.notThrows(() => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); // Loop through a large number of span creations. // This syntax may loop weird, but we're avoiding i++ and a for loop [...Array(10000)].forEach(() => { const span: any = tracer.startSpan('microspan'); span.finish(); }); }); }); test('should handle clearing spans', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); [...Array(100)].forEach(() => { const span: any = tracer.startSpan('microspan'); span.finish(); }); t.true(tracer._spans.length === 100); tracer.clear(); t.true(tracer._spans.length === 0); }); test('should handle passing in the opentracing module', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', opentracingModule: opentracing, recordCallback: () => { // }, }); // Verify that we can add tags at startSpan time. const span: any = tracer.startSpan('test'); span.finish(); t.is(tracer.opentracing, opentracing); }); test('should throw when the first arg of Span.log() is not an object', async (t: any) => { const error: any = await t.throws(() => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const span: any = tracer.startSpan('test', { tags: [], }); span.finish(); }); t.is(error.message, 'tags must be an object'); }); test('should be able to inject Context into a TextMap', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const carrier: any = {}; const span: any = tracer.startSpan('test'); span.finish(); tracer.inject(span, 'text_map', carrier); t.is(carrier['ot-tracer-spanId'], span.context().spanId); t.is(carrier['ot-tracer-parentId'], span.context().parentId); t.is(carrier['ot-tracer-traceId'], span.context().traceId); t.is(carrier['ot-tracer-referenceType'], span.context().referenceType); t.is(carrier['ot-tracer-sampled'], span.context().sampled); }); test('should be able to extract a Context from a TextMap', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const carrier: any = {}; const span: any = tracer.startSpan('test'); span.finish(); tracer.inject(span, 'text_map', carrier); t.is(carrier['ot-tracer-spanId'], span.context().spanId); t.is(carrier['ot-tracer-parentId'], span.context().parentId); t.is(carrier['ot-tracer-traceId'], span.context().traceId); t.is(carrier['ot-tracer-referenceType'], span.context().referenceType); t.is(carrier['ot-tracer-sampled'], span.context().sampled); const extractedContext: any = tracer.extract('text_map', carrier); t.is(extractedContext.spanId, span.context().spanId); t.is(extractedContext.parentId, span.context().parentId); t.is(extractedContext.traceId, span.context().traceId); t.is(extractedContext.referenceType, span.context().referenceType); t.is(extractedContext.sampled, span.context().sampled); }); test('should be able to inject Context into a TextMap', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const carrier: any = {}; const span: any = tracer.startSpan('test'); span.finish(); tracer.inject(span, 'text_map', carrier); t.is(carrier['ot-tracer-spanId'], span.context().spanId); t.is(carrier['ot-tracer-parentId'], span.context().parentId); t.is(carrier['ot-tracer-traceId'], span.context().traceId); t.is(carrier['ot-tracer-referenceType'], span.context().referenceType); t.is(carrier['ot-tracer-sampled'], span.context().sampled); }); test('should be able to join to a carrier', async (t: any) => { const tracer: Tracer = new Tracer({ accessToken: 'test', componentName: 'test', recordCallback: () => { // }, }); const carrier: any = {}; const span: any = tracer.startSpan('test'); span.finish(); tracer.inject(span, 'text_map', carrier); const childSpan: any = tracer.join('childSpan', carrier, 'text_map'); childSpan.finish(); t.is(childSpan.context().parentId, span.context().spanId); t.is(childSpan.context().traceId, span.context().traceId); t.is(childSpan.context().referenceType, 'childOf'); });