Skip to content

Commit

Permalink
SDK: add tests for exporters and processors (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
sveiss authored and fbogsany committed Oct 2, 2019
1 parent 645d99c commit d782d73
Show file tree
Hide file tree
Showing 13 changed files with 397 additions and 40 deletions.
2 changes: 1 addition & 1 deletion sdk/lib/opentelemetry/sdk/trace/export.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ module Export
require 'opentelemetry/sdk/trace/export/in_memory_span_exporter'
require 'opentelemetry/sdk/trace/export/multi_span_exporter'
require 'opentelemetry/sdk/trace/export/noop_span_exporter'
require 'opentelemetry/sdk/trace/export/simple_sampled_span_processor'
require 'opentelemetry/sdk/trace/export/simple_span_processor'
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ def initialize
@mutex = Mutex.new
end

# Returns a frozen array of the finished {Span}s, represented by
# Returns a frozen array of the finished {SpanData}s, represented by
# {io.opentelemetry.proto.trace.v1.Span}.
#
# @return [Array<Span>] a frozen array of the finished {Span}s.
# @return [Array<SpanData>] a frozen array of the finished {SpanData}s.
def finished_spans
@mutex.synchronize do
@finished_spans.clone.freeze
Expand All @@ -56,17 +56,17 @@ def reset
end
end

# Called to export sampled {Span}s.
# Called to export sampled {SpanData}s.
#
# @param [Enumerable<Span>] spans the list of sampled {Span}s to be
# @param [Enumerable<SpanData>] span_datas the list of sampled {SpanData}s to be
# exported.
# @return [Integer] the result of the export, SUCCESS or
# FAILED_NOT_RETRYABLE
def export(spans)
def export(span_datas)
@mutex.synchronize do
return FAILED_NOT_RETRYABLE if @stopped

@finished_spans.concat(spans)
@finished_spans.concat(span_datas.to_a)
end
SUCCESS
end
Expand Down
4 changes: 2 additions & 2 deletions sdk/lib/opentelemetry/sdk/trace/export/multi_span_exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Export
# received spans to a collection of SpanExporters.
#
# Can be used to export to multiple backends using the same
# SpanProcessor like a {SimpleSampledSpanProcessor} or a
# SpanProcessor like a {SimpleSpanProcessor} or a
# {BatchSpanProcessor}.
class MultiSpanExporter
def initialize(span_exporters)
Expand All @@ -29,7 +29,7 @@ def export(spans)
begin
merge_result_code(result_code, span_exporter.export(spans))
rescue => e # rubocop:disable Style/RescueStandardError
logger.warn("exception raised by export - #{e}")
OpenTelemetry.logger.warn("exception raised by export - #{e}")
FAILED_NOT_RETRYABLE
end
end
Expand Down
14 changes: 11 additions & 3 deletions sdk/lib/opentelemetry/sdk/trace/export/noop_span_exporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,28 @@ module Export
# recorded data for sampled spans in their own format.
#
# To export data an exporter MUST be registered to the {Tracer} using
# a {SimpleSampledSpanProcessor} or a {BatchSpanProcessor}.
# a {SimpleSpanProcessor} or a {BatchSpanProcessor}.
class NoopSpanExporter
def initialize
@stopped = false
end

# Called to export sampled {Span}s.
#
# @param [Enumerable<Span>] spans the list of sampled {Span}s to be
# exported.
# @return [Integer] the result of the export.
def export(spans)
SUCCESS
return SUCCESS unless @stopped

FAILED_NOT_RETRYABLE
end

# Called when {Tracer#shutdown} is called, if this exporter is
# registered to a {Tracer} object.
def shutdown; end
def shutdown
@stopped = true
end
end
end
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ module Export
# {Span} to {io.opentelemetry.proto.trace.v1.Span} and passes it to the
# configured exporter.
#
# Only spans that are sampled are converted, {OpenTelemetry::Trace::TraceFlags#sampled?} must
# Only spans that are recorded are converted, {OpenTelemetry::Trace::Span#is_recording?} must
# return true.
class SimpleSampledSpanProcessor
# Returns a new {SimpleSampledSpanProcessor} that converts spans to
class SimpleSpanProcessor
# Returns a new {SimpleSpanProcessor} that converts spans to
# proto and forwards them to the given span_exporter.
#
# @param span_exporter the (duck type) SpanExporter to where the
# sampled Spans are pushed.
# @return [SimpleSampledSpanProcessor]
# recorded Spans are pushed.
# @return [SimpleSpanProcessor]
# @raise ArgumentError if the span_exporter is nil.
def initialize(span_exporter)
raise ArgumentError, 'span_exporter' if span_exporter.nil?
Expand All @@ -47,11 +47,11 @@ def on_start(span)
#
# @param [Span] span the {Span} that just ended.
def on_finish(span)
return unless span.context.trace_flags.sampled?
return unless span.recording_events?

@span_exporter.export([span.to_span_proto])
@span_exporter.export([span.to_span_data])
rescue => e # rubocop:disable Style/RescueStandardError
logger.error("unexpected error in span.on_finish - #{e}")
OpenTelemetry.logger.error("unexpected error in span.on_finish - #{e}")
end

# Called when {Tracer#shutdown} is called.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,67 @@
require 'test_helper'

describe OpenTelemetry::SDK::Trace::Export::InMemorySpanExporter do
export = OpenTelemetry::SDK::Trace::Export

let(:span_data1) { OpenTelemetry::SDK::Trace::SpanData.new(name: 'name1') }
let(:span_data2) { OpenTelemetry::SDK::Trace::SpanData.new(name: 'name2') }

let(:exporter) { export::InMemorySpanExporter.new }

it 'accepts an Array of SpanDatas as argument to #export' do
exporter.export([span_data1, span_data2])

finished_spans = exporter.finished_spans
finished_spans[0].must_equal span_data1
finished_spans[1].must_equal span_data2
end

it 'accepts an Enumerable of SpanDatas as argument to #export' do
# An anonymous Struct serves as a handy implementor of Enumerable
enumerable = Struct.new(:span_data1, :span_data2).new
enumerable.span_data1 = span_data1
enumerable.span_data2 = span_data2

exporter.export(enumerable)

finished_spans = exporter.finished_spans
finished_spans[0].must_equal span_data1
finished_spans[1].must_equal span_data2
end

it 'freezes the return of #finished_spans' do
exporter.export([span_data1])
exporter.finished_spans.must_be :frozen?
end

it 'allows additional calls to #export after #finished_spans' do
exporter.export([span_data1])
finished_spans1 = exporter.finished_spans

exporter.export([span_data2])
finished_spans2 = exporter.finished_spans

finished_spans1.length.must_equal 1
finished_spans2.length.must_equal 2

finished_spans1[0].must_equal finished_spans2[0]
end

it 'returns success from #export' do
exporter.export([span_data1]).must_equal export::SUCCESS
end

it 'returns error from #export after #shutdown called' do
exporter.export([span_data1])
exporter.shutdown

exporter.export([span_data2]).must_equal export::FAILED_NOT_RETRYABLE
end

it 'returns an empty array from #export after #shutdown called' do
exporter.export([span_data1])
exporter.shutdown

exporter.finished_spans.length.must_equal 0
end
end
117 changes: 117 additions & 0 deletions sdk/test/opentelemetry/sdk/trace/export/multi_span_exporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,121 @@
require 'test_helper'

describe OpenTelemetry::SDK::Trace::Export::MultiSpanExporter do
export = OpenTelemetry::SDK::Trace::Export

let(:spans) { [OpenTelemetry::Trace::Span.new, OpenTelemetry::Trace::Span.new] }

let(:mock_span_exporter) { Minitest::Mock.new }
let(:mock_span_exporter2) { Minitest::Mock.new }

let(:exporter) do
export::MultiSpanExporter.new([mock_span_exporter])
end

let(:exporter_multi) do
export::MultiSpanExporter.new(
[mock_span_exporter, mock_span_exporter2]
)
end

let(:exporter_empty) do
export::MultiSpanExporter.new([])
end

it 'accepts an Array of Spans as arg to #export and forwards them' do
mock_span_exporter.expect(:export, export::SUCCESS) { |a| a.to_a == spans }

exporter.export(spans).must_equal export::SUCCESS
mock_span_exporter.verify
end

it 'accepts an Enumerable of Spans as arg to #export and forwards them' do
# An anonymous Struct serves as a handy implementor of Enumerable
enumerable = Struct.new(:span0, :span1).new
enumerable.span0 = spans[0]
enumerable.span1 = spans[1]

mock_span_exporter.expect(:export, export::SUCCESS) { |a| a.to_a == spans }

exporter.export(enumerable).must_equal export::SUCCESS
mock_span_exporter.verify
end

it 'forwards spans from #export to multiple exporters' do
mock_span_exporter.expect(:export, export::SUCCESS) { |a| a.to_a == spans }
mock_span_exporter2.expect(:export, export::SUCCESS) { |a| a.to_a == spans }

exporter_multi.export(spans).must_equal export::SUCCESS
mock_span_exporter.verify
mock_span_exporter2.verify
end

it 'returns an error from #export if one exporter fails (retryable)' do
mock_span_exporter.expect :export, export::SUCCESS, [Object]
mock_span_exporter2.expect :export, export::FAILED_RETRYABLE, [Object]

exporter_multi.export(spans).must_equal export::FAILED_RETRYABLE
mock_span_exporter.verify
mock_span_exporter2.verify
end

it 'returns an error from #export if one exporter fails (not retryable)' do
mock_span_exporter.expect :export, export::SUCCESS, [Object]
mock_span_exporter2.expect :export, export::FAILED_NOT_RETRYABLE, [Object]

exporter_multi.export(spans).must_equal export::FAILED_NOT_RETRYABLE
mock_span_exporter.verify
mock_span_exporter2.verify
end

it 'returns the worst error from #export if multiple exporters fail' do
# retryable error first
mock_span_exporter.expect :export, export::FAILED_RETRYABLE, [Object]
mock_span_exporter2.expect :export, export::FAILED_NOT_RETRYABLE, [Object]

exporter_multi.export(spans).must_equal export::FAILED_NOT_RETRYABLE
mock_span_exporter.verify
mock_span_exporter2.verify
end

it 'returns the worst error from #export (reversed order)' do
# non-retryable error first
mock_span_exporter.expect :export, export::FAILED_NOT_RETRYABLE, [Object]
mock_span_exporter2.expect :export, export::FAILED_RETRYABLE, [Object]

exporter_multi.export(spans).must_equal export::FAILED_NOT_RETRYABLE
mock_span_exporter.verify
mock_span_exporter2.verify
end

it 'synthesizes an error if an exporter raises an exception' do
def mock_span_exporter.export(_)
raise ArgumentError
end

logger_mock = Minitest::Mock.new
logger_mock.expect :warn, nil, [/ArgumentError/]
OpenTelemetry.stub :logger, logger_mock do
exporter.export(spans).must_equal export::FAILED_NOT_RETRYABLE
end

logger_mock.verify
end

it 'forwards a #shutdown call to all exporters' do
mock_span_exporter.expect :shutdown, nil
mock_span_exporter2.expect :shutdown, nil

exporter_multi.shutdown
mock_span_exporter.verify
mock_span_exporter2.verify
end

it 'returns success on #export with empty exporter list' do
exporter_empty.export(spans).must_equal export::SUCCESS
end

it 'accepts calls to #shutdown with empty exporter list' do
exporter_empty.shutdown
end
end
26 changes: 26 additions & 0 deletions sdk/test/opentelemetry/sdk/trace/export/noop_span_exporter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,30 @@
require 'test_helper'

describe OpenTelemetry::SDK::Trace::Export::NoopSpanExporter do
export = OpenTelemetry::SDK::Trace::Export

let(:spans) { [OpenTelemetry::Trace::Span.new, OpenTelemetry::Trace::Span.new] }
let(:exporter) { export::NoopSpanExporter.new }

it 'accepts an Array of Spans as arg to #export and succeeds' do
exporter.export(spans).must_equal export::SUCCESS
end

it 'accepts an Enumerable of Spans as arg to #export and succeeds' do
# An anonymous Struct serves as a handy implementor of Enumerable
enumerable = Struct.new(:span0, :span1).new
enumerable.span0 = spans[0]
enumerable.span1 = spans[1]

exporter.export(enumerable).must_equal export::SUCCESS
end

it 'accepts calls to #shutdown' do
exporter.shutdown
end

it 'fails to export after shutdown' do
exporter.shutdown
exporter.export(spans).must_equal export::FAILED_NOT_RETRYABLE
end
end

This file was deleted.

Loading

0 comments on commit d782d73

Please sign in to comment.