diff --git a/lib/datadog/ci/test_visibility/serializer/base.rb b/lib/datadog/ci/test_visibility/serializer/base.rb deleted file mode 100644 index 01763fa3..00000000 --- a/lib/datadog/ci/test_visibility/serializer/base.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -module Datadog - module CI - module TestVisibility - module Serializer - class Base - attr_reader :trace, :span - - def initialize(trace, span) - @trace = trace - @span = span - end - - def runtime_id - @trace.runtime_id - end - - # Used for serialization - # @return [Integer] in nanoseconds since Epoch - def time_nano(time) - time.to_i * 1000000000 + time.nsec - end - - # Used for serialization - # @return [Integer] in nanoseconds since Epoch - def duration_nano(duration) - (duration * 1e9).to_i - end - end - end - end - end -end diff --git a/lib/datadog/ci/test_visibility/serializer/span.rb b/lib/datadog/ci/test_visibility/serializer/span.rb deleted file mode 100644 index 9da993d6..00000000 --- a/lib/datadog/ci/test_visibility/serializer/span.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require_relative "base" - -module Datadog - module CI - module TestVisibility - module Serializer - class Span < Base - def to_msgpack(packer = nil) - packer ||= MessagePack::Packer.new - - packer.write_map_header(3) - - packer.write("type") - packer.write("span") - - packer.write("version") - packer.write(1) - - packer.write("content") - - packer.write_map_header(12) - - packer.write("trace_id") - packer.write(@trace.id) - - packer.write("span_id") - packer.write(@span.id) - - packer.write("parent_id") - packer.write(@span.parent_id) - - packer.write("name") - packer.write(@span.name) - - packer.write("resource") - packer.write(@span.resource) - - packer.write("service") - packer.write(@span.service) - - packer.write("type") - packer.write(@span.type) - - packer.write("error") - packer.write(@span.status) - - packer.write("start") - packer.write(time_nano(@span.start_time)) - - packer.write("duration") - packer.write(duration_nano(@span.duration)) - - packer.write("meta") - packer.write(@span.meta) - - packer.write("metrics") - packer.write(@span.metrics) - end - end - end - end - end -end diff --git a/lib/datadog/ci/test_visibility/serializer/test.rb b/lib/datadog/ci/test_visibility/serializer/test.rb deleted file mode 100644 index 48d54c9e..00000000 --- a/lib/datadog/ci/test_visibility/serializer/test.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -require_relative "base" -require_relative "../../ext/test" - -module Datadog - module CI - module TestVisibility - module Serializer - class Test < Base - def to_msgpack(packer = nil) - packer ||= MessagePack::Packer.new - - packer.write_map_header(3) - - packer.write("type") - packer.write("test") - - packer.write("version") - packer.write(1) - - packer.write("content") - - packer.write_map_header(10) - - packer.write("trace_id") - packer.write(@trace.id) - - packer.write("span_id") - packer.write(@span.id) - - packer.write("name") - packer.write("#{@span.get_tag(Ext::Test::TAG_FRAMEWORK)}.test") - - packer.write("resource") - packer.write("#{@span.get_tag(Ext::Test::TAG_SUITE)}.#{@span.get_tag(Ext::Test::TAG_NAME)}") - - packer.write("service") - packer.write(@span.service) - - packer.write("type") - packer.write("test") - - packer.write("start") - packer.write(time_nano(@span.start_time)) - - packer.write("duration") - packer.write(duration_nano(@span.duration)) - - packer.write("meta") - packer.write(@span.meta) - - packer.write("metrics") - packer.write(@span.metrics) - end - end - end - end - end -end diff --git a/lib/datadog/ci/test_visibility/serializers.rb b/lib/datadog/ci/test_visibility/serializers.rb new file mode 100644 index 00000000..294241ef --- /dev/null +++ b/lib/datadog/ci/test_visibility/serializers.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative "serializers/test_v1" +require_relative "serializers/span" + +module Datadog + module CI + module TestVisibility + module Serializers + module_function + + def convert_trace_to_serializable_events(trace) + trace.spans.map { |span| convert_span_to_serializable_event(trace, span) } + end + + # for test suite visibility we might need to make it configurable + def convert_span_to_serializable_event(trace, span) + case span.type + when Datadog::CI::Ext::AppTypes::TYPE_TEST + Serializers::TestV1.new(trace, span) + else + Serializers::Span.new(trace, span) + end + end + end + end + end +end diff --git a/lib/datadog/ci/test_visibility/serializers/base.rb b/lib/datadog/ci/test_visibility/serializers/base.rb new file mode 100644 index 00000000..c5cfaa05 --- /dev/null +++ b/lib/datadog/ci/test_visibility/serializers/base.rb @@ -0,0 +1,136 @@ +# frozen_string_literal: true + +module Datadog + module CI + module TestVisibility + module Serializers + class Base + attr_reader :trace, :span + + def initialize(trace, span) + @trace = trace + @span = span + end + + def to_msgpack(packer = nil) + packer ||= MessagePack::Packer.new + + packer.write_map_header(3) + + write_field(packer, "type") + write_field(packer, "version") + + packer.write("content") + packer.write_map_header(content_fields_count) + + content_fields.each do |field| + if field.is_a?(Hash) + field.each do |field_name, method| + write_field(packer, field_name, method) + end + else + write_field(packer, field) + end + end + end + + def content_fields + [] + end + + def runtime_id + @trace.runtime_id + end + + def trace_id + @trace.id + end + + def span_id + @span.id + end + + def parent_id + @span.parent_id + end + + def type + end + + def version + 1 + end + + def span_type + @span.type + end + + def name + @span.name + end + + def resource + @span.resource + end + + def service + @span.service + end + + def start + time_nano(@span.start_time) + end + + def duration + duration_nano(@span.duration) + end + + def meta + @span.meta + end + + def metrics + @span.metrics + end + + def error + @span.status + end + + private + + def write_field(packer, field_name, method = nil) + method ||= field_name + + packer.write(field_name) + packer.write(send(method)) + end + + # Used for serialization + # @return [Integer] in nanoseconds since Epoch + def time_nano(time) + time.to_i * 1000000000 + time.nsec + end + + # Used for serialization + # @return [Integer] in nanoseconds since Epoch + def duration_nano(duration) + (duration * 1e9).to_i + end + + def content_fields_count + res = 0 + content_fields.each do |field| + res += if field.is_a?(Hash) + field.size + else + 1 + end + end + res + end + end + end + end + end +end diff --git a/lib/datadog/ci/test_visibility/serializers/span.rb b/lib/datadog/ci/test_visibility/serializers/span.rb new file mode 100644 index 00000000..07bd45e4 --- /dev/null +++ b/lib/datadog/ci/test_visibility/serializers/span.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require_relative "base" + +module Datadog + module CI + module TestVisibility + module Serializers + class Span < Base + def content_fields + @content_fields ||= [ + "trace_id", "span_id", "parent_id", "name", + "resource", "service", "error", "start", + "duration", "meta", "metrics", + "type" => "span_type" + ] + end + + def type + "span" + end + end + end + end + end +end diff --git a/lib/datadog/ci/test_visibility/serializers/test_v1.rb b/lib/datadog/ci/test_visibility/serializers/test_v1.rb new file mode 100644 index 00000000..7200e75b --- /dev/null +++ b/lib/datadog/ci/test_visibility/serializers/test_v1.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require_relative "base" +require_relative "../../ext/test" + +module Datadog + module CI + module TestVisibility + module Serializers + class TestV1 < Base + def content_fields + @content_fields ||= [ + "trace_id", "span_id", "name", "resource", "service", + "start", "duration", "meta", "metrics", "error", + "type" => "span_type" + ] + end + + def type + "test" + end + + def name + "#{@span.get_tag(Ext::Test::TAG_FRAMEWORK)}.test" + end + + def resource + "#{@span.get_tag(Ext::Test::TAG_SUITE)}.#{@span.get_tag(Ext::Test::TAG_NAME)}" + end + end + end + end + end +end diff --git a/lib/datadog/ci/test_visibility/something_that_converts_traces.rb b/lib/datadog/ci/test_visibility/something_that_converts_traces.rb deleted file mode 100644 index 71a584b5..00000000 --- a/lib/datadog/ci/test_visibility/something_that_converts_traces.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -require_relative "serializer/test" -require_relative "serializer/span" - -module Datadog - module CI - module TestVisibility - module SomethingThatConvertsTraces - module_function - - def convert(trace) - trace.spans.map { |span| convert_span(trace, span) } - end - - def convert_span(trace, span) - case span.type - when Datadog::CI::Ext::AppTypes::TYPE_TEST - Serializer::Test.new(trace, span) - else - Serializer::Span.new(trace, span) - end - end - end - end - end -end diff --git a/spec/datadog/ci/test_visibility/serializer/span_spec.rb b/spec/datadog/ci/test_visibility/serializers/span_spec.rb similarity index 64% rename from spec/datadog/ci/test_visibility/serializer/span_spec.rb rename to spec/datadog/ci/test_visibility/serializers/span_spec.rb index dfa572c9..3c96b7f7 100644 --- a/spec/datadog/ci/test_visibility/serializer/span_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/span_spec.rb @@ -1,7 +1,7 @@ -require_relative "../../../../../lib/datadog/ci/test_visibility/serializer/span" +require_relative "../../../../../lib/datadog/ci/test_visibility/serializers/span" require_relative "../../../../../lib/datadog/ci/recorder" -RSpec.describe Datadog::CI::TestVisibility::Serializer::Span do +RSpec.describe Datadog::CI::TestVisibility::Serializers::Span do include_context "CI mode activated" do let(:integration_name) { :rspec } end @@ -13,31 +13,13 @@ let(:tracer_span) do spans.find { |span| span.type != "test" } end + subject { described_class.new(trace, tracer_span) } describe "#to_msgpack" do context "traced a single test execution with Recorder" do before do - Datadog::CI::Recorder.trace( - "rspec.example", - { - span_options: { - resource: "test_add", - service: "rspec-test-suite" - }, - framework: "rspec", - framework_version: "3.0.0", - test_name: "test_add", - test_suite: "calculator_tests.rb", - test_type: "test" - } - ) do |span| - Datadog::Tracing.trace("http-call", type: "http", service: "net-http") do |span, trace| - span.set_tag("custom_tag", "custom_tag_value") - end - - Datadog::CI::Recorder.passed!(span) - end + produce_test_trace(with_http_span: true) end let(:payload) { MessagePack.unpack(MessagePack.pack(subject)) } @@ -53,8 +35,8 @@ expect(content).to include( { "trace_id" => trace.id, - "span_id" => tracer_span.id, - "parent_id" => test_span.id, + "span_id" => first_other_span.id, + "parent_id" => first_test_span.id, "name" => "http-call", "service" => "net-http", "type" => "http", diff --git a/spec/datadog/ci/test_visibility/serializer/test_spec.rb b/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb similarity index 70% rename from spec/datadog/ci/test_visibility/serializer/test_spec.rb rename to spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb index d06f81ee..e043920a 100644 --- a/spec/datadog/ci/test_visibility/serializer/test_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb @@ -1,7 +1,7 @@ -require_relative "../../../../../lib/datadog/ci/test_visibility/serializer/test" +require_relative "../../../../../lib/datadog/ci/test_visibility/serializers/test_v1" require_relative "../../../../../lib/datadog/ci/recorder" -RSpec.describe Datadog::CI::TestVisibility::Serializer::Test do +RSpec.describe Datadog::CI::TestVisibility::Serializers::TestV1 do include_context "CI mode activated" do let(:integration_name) { :rspec } end @@ -11,22 +11,7 @@ describe "#to_msgpack" do context "traced a single test execution with Recorder" do before do - Datadog::CI::Recorder.trace( - "rspec.example", - { - span_options: { - resource: "test_add", - service: "rspec-test-suite" - }, - framework: "rspec", - framework_version: "3.0.0", - test_name: "test_add", - test_suite: "calculator_tests", - test_type: "test" - } - ) do |span| - Datadog::CI::Recorder.passed!(span) - end + produce_test_trace end let(:payload) { MessagePack.unpack(MessagePack.pack(subject)) } diff --git a/spec/datadog/ci/test_visibility/serializers_spec.rb b/spec/datadog/ci/test_visibility/serializers_spec.rb new file mode 100644 index 00000000..0297c01b --- /dev/null +++ b/spec/datadog/ci/test_visibility/serializers_spec.rb @@ -0,0 +1,40 @@ +require_relative "../../../../lib/datadog/ci/test_visibility/serializers" +require_relative "../../../../lib/datadog/ci/recorder" + +RSpec.describe Datadog::CI::TestVisibility::Serializers do + include_context "CI mode activated" do + let(:integration_name) { :rspec } + end + + subject { described_class.convert_trace_to_serializable_events(trace) } + + describe ".convert_trace_to_serializable_events" do + context "traced a single test execution with Recorder" do + before do + produce_test_trace(with_http_span: true) + end + + let(:payload) { MessagePack.unpack(MessagePack.pack(subject)) } + + it "converts trace to an array of serializable events" do + expect(subject.count).to eq(2) + end + + context "test event is present" do + let(:test_event) { subject.find { |event| event.type == "test" } } + + it "contains test span" do + expect(test_event.span_id).to eq(first_test_span.id) + end + end + + context "span event is present" do + let(:span_event) { subject.find { |event| event.type == "span" } } + + it "contains http span" do + expect(span_event.span_id).to eq(first_other_span.id) + end + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 47ba5b5e..fd20be7e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -12,7 +12,6 @@ require_relative "support/log_helpers" require_relative "support/tracer_helpers" require_relative "support/span_helpers" -require_relative "support/test_helpers" require_relative "support/platform_helpers" require_relative "support/git_helpers" require_relative "support/provider_test_helpers" @@ -34,8 +33,6 @@ config.include TracerHelpers config.include SpanHelpers - config.include TestHelpers::RSpec::Integration, :integration - # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" diff --git a/spec/support/faux_transport.rb b/spec/support/faux_transport.rb deleted file mode 100644 index 1474343f..00000000 --- a/spec/support/faux_transport.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "ddtrace/transport/http" - -# FauxTransport is a dummy Datadog::Transport that doesn't send data to an agent. -class FauxTransport < Datadog::Transport::HTTP::Client - def initialize(*) - end - - def send_traces(*) - # Emulate an OK response - [Datadog::Transport::HTTP::Traces::Response.new( - Datadog::Transport::HTTP::Adapters::Net::Response.new( - Net::HTTPResponse.new(1.0, 200, "OK") - ) - )] - end -end diff --git a/spec/support/faux_writer.rb b/spec/support/faux_writer.rb deleted file mode 100644 index ebba7e36..00000000 --- a/spec/support/faux_writer.rb +++ /dev/null @@ -1,54 +0,0 @@ -require_relative "faux_transport" - -# FauxWriter is a dummy writer that buffers spans locally. -class FauxWriter < Datadog::Tracing::Writer - def initialize(options = {}) - options[:transport] ||= FauxTransport.new - options[:call_original] ||= true - @options = options - - super if options[:call_original] - @mutex = Mutex.new - - # easy access to registered components - @traces = [] - end - - def write(trace) - @mutex.synchronize do - super(trace) if @options[:call_original] - @traces << trace - end - end - - def traces(action = :clear) - traces = @traces - @traces = [] if action == :clear - traces - end - - def spans(action = :clear) - @mutex.synchronize do - traces = @traces - @traces = [] if action == :clear - spans = traces.collect(&:spans).flatten - # sort the spans to avoid test flakiness - - spans.sort! do |a, b| - if a.name == b.name - if a.resource == b.resource - if a.start_time == b.start_time - a.end_time <=> b.end_time - else - a.start_time <=> b.start_time - end - else - a.resource <=> b.resource - end - else - a.name <=> b.name - end - end - end - end -end diff --git a/spec/support/test_helpers.rb b/spec/support/test_helpers.rb deleted file mode 100644 index 037ecf6f..00000000 --- a/spec/support/test_helpers.rb +++ /dev/null @@ -1,31 +0,0 @@ -module TestHelpers - module_function - - # Integration tests are normally expensive (time-wise or resource-wise). - # They run in CI by default. - def run_integration_tests? - ENV["TEST_DATADOG_INTEGRATION"] - end - - module RSpec - # RSpec extension to allow for declaring integration tests - # using example group parameters: - # - # ```ruby - # describe 'end-to-end foo test', :integration do - # ... - # end - # ``` - module Integration - def self.included(base) - base.class_exec do - before do - unless run_integration_tests? - skip("Integration tests can be enabled by setting the environment variable `TEST_DATADOG_INTEGRATION=1`") - end - end - end - end - end - end -end diff --git a/spec/support/tracer_helpers.rb b/spec/support/tracer_helpers.rb index d57a96a7..7414909c 100644 --- a/spec/support/tracer_helpers.rb +++ b/spec/support/tracer_helpers.rb @@ -8,6 +8,43 @@ def tracer Datadog::Tracing.send(:tracer) end + def produce_test_trace( + framework: "rspec", operation: "rspec.example", + test_name: "test_add", test_suite: "calculator_tests", + service: "rspec-test-suite", with_http_span: false + ) + Datadog::CI::Recorder.trace( + operation, + { + span_options: { + resource: test_name, + service: service + }, + framework: framework, + framework_version: "1.0.0", + test_name: test_name, + test_suite: test_suite, + test_type: "test" + } + ) do |span| + if with_http_span + Datadog::Tracing.trace("http-call", type: "http", service: "net-http") do |span, trace| + span.set_tag("custom_tag", "custom_tag_value") + end + end + + Datadog::CI::Recorder.passed!(span) + end + end + + def first_test_span + spans.find { |span| span.type == "test" } + end + + def first_other_span + spans.find { |span| span.type != "test" } + end + # Returns spans and caches it (similar to +let(:spans)+). def traces @traces ||= fetch_traces