hawkly
Version:
An OpenTracing compatible tracer for hawkly.io
431 lines (372 loc) • 12.9 kB
text/typescript
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');
});