Skip to content

Commit

Permalink
test(otlp-transformer): add tests for trace serializer
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc committed Mar 15, 2024
1 parent 79256ed commit 76c4667
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 64 deletions.
18 changes: 9 additions & 9 deletions experimental/packages/otlp-transformer/src/json/serializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ export const JsonTraceSerializer: ISerializer<
const encoder = new TextEncoder();
return encoder.encode(JSON.stringify(request));
},
deserializeResponse: (_arg: Uint8Array) => {
// Not yet implemented
return {};
deserializeResponse: (arg: Uint8Array) => {
const decoder = new TextDecoder();
return JSON.parse(decoder.decode(arg)) as IExportTraceServiceResponse;
},
};

Expand All @@ -54,9 +54,9 @@ export const JsonMetricsSerializer: ISerializer<
const encoder = new TextEncoder();
return encoder.encode(JSON.stringify(request));
},
deserializeResponse: (_arg: Uint8Array) => {
// Not yet implemented
return {};
deserializeResponse: (arg: Uint8Array) => {
const decoder = new TextDecoder();
return JSON.parse(decoder.decode(arg)) as IExportMetricsServiceResponse;
},
};

Expand All @@ -72,8 +72,8 @@ export const JsonLogsSerializer: ISerializer<
const encoder = new TextEncoder();
return encoder.encode(JSON.stringify(request));
},
deserializeResponse: (_arg: Uint8Array) => {
// Not yet implemented
return {};
deserializeResponse: (arg: Uint8Array) => {
const decoder = new TextDecoder();
return JSON.parse(decoder.decode(arg)) as IExportLogsServiceResponse;
},
};
313 changes: 258 additions & 55 deletions experimental/packages/otlp-transformer/test/trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as root from '../src/generated/root';
import { SpanKind, SpanStatusCode, TraceFlags } from '@opentelemetry/api';
import { TraceState, hexToBinary } from '@opentelemetry/core';
import { Resource } from '@opentelemetry/resources';
Expand All @@ -23,6 +24,8 @@ import {
createExportTraceServiceRequest,
ESpanKind,
EStatusCode,
ProtobufTraceSerializer,
JsonTraceSerializer,
} from '../src';

function createExpectedSpanJson(options: OtlpEncodingOptions) {
Expand Down Expand Up @@ -133,69 +136,173 @@ function createExpectedSpanJson(options: OtlpEncodingOptions) {
};
}

describe('Trace', () => {
describe('createExportTraceServiceRequest', () => {
let resource: Resource;
let span: ReadableSpan;
/**
* utility function to convert a string representing a hex value to a base64 string
* that represents the bytes of that hex value. This is needed as we need to support Node.js 14
* where btoa() does not exist, and the Browser, where Buffer does not exist.
* @param hexStr
*/
function toBase64(hexStr: string) {
if (typeof btoa !== 'undefined') {
const decoder = new TextDecoder('utf8');
return btoa(decoder.decode(hexToBinary(hexStr)));
}

beforeEach(() => {
resource = new Resource({
'resource-attribute': 'resource attribute value',
});
span = {
spanContext: () => ({
spanId: '0000000000000002',
traceFlags: TraceFlags.SAMPLED,
traceId: '00000000000000000000000000000001',
isRemote: false,
traceState: new TraceState('span=bar'),
}),
parentSpanId: '0000000000000001',
attributes: { 'string-attribute': 'some attribute value' },
duration: [1, 300000000],
endTime: [1640715558, 642725388],
ended: true,
events: [
{
name: 'some event',
time: [1640715558, 542725388],
attributes: {
'event-attribute': 'some string value',
return Buffer.from(hexToBinary(hexStr)).toString('base64');
}

function createExpectedSpanProtobuf() {
const startTime = 1640715557342725400;
const endTime = 1640715558642725400;
const eventTime = 1640715558542725400;

const traceId = toBase64('00000000000000000000000000000001');
const spanId = toBase64('0000000000000002');
const parentSpanId = toBase64('0000000000000001');
const linkSpanId = toBase64('0000000000000003');
const linkTraceId = toBase64('00000000000000000000000000000002');

return {
resourceSpans: [
{
resource: {
attributes: [
{
key: 'resource-attribute',
value: { stringValue: 'resource attribute value' },
},
},
],
instrumentationLibrary: {
name: 'myLib',
version: '0.1.0',
schemaUrl: 'http://url.to.schema',
],
droppedAttributesCount: 0,
},
kind: SpanKind.CLIENT,
links: [
scopeSpans: [
{
context: {
spanId: '0000000000000003',
traceId: '00000000000000000000000000000002',
traceFlags: TraceFlags.SAMPLED,
isRemote: false,
traceState: new TraceState('link=foo'),
},
attributes: {
'link-attribute': 'string value',
},
scope: { name: 'myLib', version: '0.1.0' },
spans: [
{
traceId: traceId,
spanId: spanId,
traceState: 'span=bar',
parentSpanId: parentSpanId,
name: 'span-name',
kind: ESpanKind.SPAN_KIND_CLIENT,
links: [
{
droppedAttributesCount: 0,
spanId: linkSpanId,
traceId: linkTraceId,
traceState: 'link=foo',
attributes: [
{
key: 'link-attribute',
value: {
stringValue: 'string value',
},
},
],
},
],
startTimeUnixNano: startTime,
endTimeUnixNano: endTime,
events: [
{
droppedAttributesCount: 0,
attributes: [
{
key: 'event-attribute',
value: {
stringValue: 'some string value',
},
},
],
name: 'some event',
timeUnixNano: eventTime,
},
],
attributes: [
{
key: 'string-attribute',
value: { stringValue: 'some attribute value' },
},
],
droppedAttributesCount: 0,
droppedEventsCount: 0,
droppedLinksCount: 0,
status: {
code: EStatusCode.STATUS_CODE_OK,
},
},
],
schemaUrl: 'http://url.to.schema',
},
],
name: 'span-name',
resource,
startTime: [1640715557, 342725388],
status: {
code: SpanStatusCode.OK,
},
droppedAttributesCount: 0,
droppedEventsCount: 0,
droppedLinksCount: 0,
};
},
],
};
}

describe('Trace', () => {
let resource: Resource;
let span: ReadableSpan;

beforeEach(() => {
resource = new Resource({
'resource-attribute': 'resource attribute value',
});
span = {
spanContext: () => ({
spanId: '0000000000000002',
traceFlags: TraceFlags.SAMPLED,
traceId: '00000000000000000000000000000001',
isRemote: false,
traceState: new TraceState('span=bar'),
}),
parentSpanId: '0000000000000001',
attributes: { 'string-attribute': 'some attribute value' },
duration: [1, 300000000],
endTime: [1640715558, 642725388],
ended: true,
events: [
{
name: 'some event',
time: [1640715558, 542725388],
attributes: {
'event-attribute': 'some string value',
},
},
],
instrumentationLibrary: {
name: 'myLib',
version: '0.1.0',
schemaUrl: 'http://url.to.schema',
},
kind: SpanKind.CLIENT,
links: [
{
context: {
spanId: '0000000000000003',
traceId: '00000000000000000000000000000002',
traceFlags: TraceFlags.SAMPLED,
isRemote: false,
traceState: new TraceState('link=foo'),
},
attributes: {
'link-attribute': 'string value',
},
},
],
name: 'span-name',
resource,
startTime: [1640715557, 342725388],
status: {
code: SpanStatusCode.OK,
},
droppedAttributesCount: 0,
droppedEventsCount: 0,
droppedLinksCount: 0,
};
});

describe('createExportTraceServiceRequest', () => {
it('returns null on an empty list', () => {
assert.deepStrictEqual(
createExportTraceServiceRequest([], { useHex: true }),
Expand Down Expand Up @@ -343,4 +450,100 @@ describe('Trace', () => {
});
});
});

describe('ProtobufTracesSerializer', function () {
it('serializes an export request', () => {
const serialized = ProtobufTraceSerializer.serializeRequest([span]);
assert.ok(serialized, 'serialized response is undefined');
const decoded =
root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.decode(
serialized
);

const expected = createExpectedSpanProtobuf();
const decodedObj =
root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest.toObject(
decoded,
{
// This incurs some precision loss that's taken into account in createExpectedSpanProtobuf()
// Using String here will incur the same precision loss on browser only, using Number to prevent having to
// have different assertions for browser and Node.js
longs: Number,
// Convert to String (Base64) as otherwise the type will be different for Node.js (Buffer) and Browser (Uint8Array)
// and this fails assertions.
bytes: String,
}
);

assert.deepStrictEqual(decodedObj, expected);
});

it('deserializes a response', () => {
const protobufSerializedResponse =
root.opentelemetry.proto.collector.trace.v1.ExportTraceServiceResponse.encode(
{
partialSuccess: {
errorMessage: 'foo',
rejectedSpans: 1,
},
}
).finish();

const deserializedResponse = ProtobufTraceSerializer.deserializeResponse(
protobufSerializedResponse
);

assert.ok(
deserializedResponse.partialSuccess,
'partialSuccess not present in the deserialized message'
);
assert.equal(deserializedResponse.partialSuccess.errorMessage, 'foo');
assert.equal(
Number(deserializedResponse.partialSuccess.rejectedSpans),
1
);
});
});

describe('JsonTracesSerializer', function () {
it('serializes an export request', () => {
// stringify, then parse to remove undefined keys in the expected JSON
const expected = JSON.parse(
JSON.stringify(
createExpectedSpanJson({
useHex: true,
useLongBits: false,
})
)
);
const serialized = JsonTraceSerializer.serializeRequest([span]);

const decoder = new TextDecoder();
assert.deepStrictEqual(JSON.parse(decoder.decode(serialized)), expected);
});

it('deserializes a response', () => {
const expectedResponse = {
partialSuccess: {
errorMessage: 'foo',
rejectedSpans: 1,
},
};
const encoder = new TextEncoder();
const encodedResponse = encoder.encode(JSON.stringify(expectedResponse));

const deserializedResponse =
JsonTraceSerializer.deserializeResponse(encodedResponse);

assert.ok(
deserializedResponse.partialSuccess,
'partialSuccess not present in the deserialized message'
);
assert.equal(deserializedResponse.partialSuccess.errorMessage, 'foo');
assert.equal(
Number(deserializedResponse.partialSuccess.rejectedSpans),
1
);
});
});
});

0 comments on commit 76c4667

Please sign in to comment.