From d53f174865fa67889ac8345934d811aceec5c208 Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Tue, 28 Nov 2023 12:11:01 +0100 Subject: [PATCH 1/5] test for the current status quo: when multiple tests are traced within a session they belong to a single trace --- .../serializers/factories/test_level_spec.rb | 2 +- .../factories/test_suite_level_spec.rb | 2 +- .../test_visibility/serializers/span_spec.rb | 2 +- .../serializers/test_session_spec.rb | 2 +- .../serializers/test_v1_spec.rb | 2 +- .../serializers/test_v2_spec.rb | 35 +++++++++++++++++-- .../test_visibility_event_serialized.rb | 10 +++++- spec/support/tracer_helpers.rb | 28 +++++++++------ 8 files changed, 65 insertions(+), 18 deletions(-) diff --git a/spec/datadog/ci/test_visibility/serializers/factories/test_level_spec.rb b/spec/datadog/ci/test_visibility/serializers/factories/test_level_spec.rb index 72555693..b3325858 100644 --- a/spec/datadog/ci/test_visibility/serializers/factories/test_level_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/factories/test_level_spec.rb @@ -7,7 +7,7 @@ let(:integration_name) { :rspec } end - subject { described_class.serializer(trace, span) } + subject { described_class.serializer(trace_for_span(span), span) } describe ".convert_trace_to_serializable_events" do context "traced a single test execution with Recorder" do diff --git a/spec/datadog/ci/test_visibility/serializers/factories/test_suite_level_spec.rb b/spec/datadog/ci/test_visibility/serializers/factories/test_suite_level_spec.rb index 3bd9c945..7b5a1a8b 100644 --- a/spec/datadog/ci/test_visibility/serializers/factories/test_suite_level_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/factories/test_suite_level_spec.rb @@ -12,7 +12,7 @@ produce_test_session_trace(with_http_span: true) end - subject { described_class.serializer(trace, ci_span) } + subject { described_class.serializer(trace_for_span(ci_span), ci_span) } describe ".convert_trace_to_serializable_events" do context "with a session span" do diff --git a/spec/datadog/ci/test_visibility/serializers/span_spec.rb b/spec/datadog/ci/test_visibility/serializers/span_spec.rb index cb3fe02a..bddaa76e 100644 --- a/spec/datadog/ci/test_visibility/serializers/span_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/span_spec.rb @@ -7,7 +7,7 @@ end include_context "Test visibility event serialized" do - subject { described_class.new(trace, tracer_span) } + subject { described_class.new(trace_for_span(tracer_span), tracer_span) } end let(:test_span) do diff --git a/spec/datadog/ci/test_visibility/serializers/test_session_spec.rb b/spec/datadog/ci/test_visibility/serializers/test_session_spec.rb index bb05f5e9..a6a3a1b6 100644 --- a/spec/datadog/ci/test_visibility/serializers/test_session_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/test_session_spec.rb @@ -8,7 +8,7 @@ end include_context "Test visibility event serialized" do - subject { described_class.new(trace, test_session_span) } + subject { described_class.new(trace_for_span(test_session_span), test_session_span) } end describe "#to_msgpack" do diff --git a/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb b/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb index 76fae829..7232d9f6 100644 --- a/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb @@ -7,7 +7,7 @@ end include_context "Test visibility event serialized" do - subject { described_class.new(trace, span) } + subject { described_class.new(trace_for_span(span), span) } end describe "#to_msgpack" do diff --git a/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb b/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb index 46732848..28165b3c 100644 --- a/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb @@ -7,7 +7,7 @@ end include_context "Test visibility event serialized" do - subject { described_class.new(trace, first_test_span) } + subject { described_class.new(trace_for_span(first_test_span), first_test_span) } end describe "#to_msgpack" do @@ -26,13 +26,14 @@ "name" => "rspec.test", "service" => "rspec-test-suite", "type" => "test", - "resource" => "calculator_tests.test_add", + "resource" => "calculator_tests.test_add.run.0", "test_session_id" => test_session_span.id.to_s } ) expect(meta).to include( { + "test.name" => "test_add.run.0", "test.framework" => "rspec", "test.status" => "pass", "_dd.origin" => "ciapp-test", @@ -45,6 +46,36 @@ end end + context "trace several tests executions with Recorder" do + let(:test_spans) { spans.select { |span| span.type == "test" } } + subject { test_spans.map { |span| described_class.new(trace_for_span(span), span) } } + + before do + produce_test_session_trace(tests_count: 2) + end + + it "serializes both tests to msgpack" do + msgpack_jsons.each_with_index do |msgpack_json, index| + expect(msgpack_json["content"]).to include( + { + "trace_id" => test_spans[index].trace_id, + "span_id" => test_spans[index].id, + "name" => "rspec.test", + "service" => "rspec-test-suite", + "type" => "test", + "resource" => "calculator_tests.test_add.run.#{index}", + "test_session_id" => test_session_span.id.to_s + } + ) + end + end + + it "all tests have the same trace_id" do + unique_trace_ids = msgpack_jsons.map { |msgpack_json| msgpack_json["content"]["trace_id"] }.uniq + expect(unique_trace_ids.size).to eq(1) + end + end + context "trace a failed test" do before do produce_test_session_trace(result: "FAILED", exception: StandardError.new("1 + 2 are not equal to 5")) diff --git a/spec/support/test_visibility_event_serialized.rb b/spec/support/test_visibility_event_serialized.rb index af6de9c2..e4a76618 100644 --- a/spec/support/test_visibility_event_serialized.rb +++ b/spec/support/test_visibility_event_serialized.rb @@ -1,7 +1,15 @@ RSpec.shared_context "Test visibility event serialized" do subject {} - let(:msgpack_json) { MessagePack.unpack(MessagePack.pack(subject)) } + let(:msgpack_jsons) do + if subject.is_a?(Array) + subject.map { |s| MessagePack.unpack(MessagePack.pack(s)) } + else + [MessagePack.unpack(MessagePack.pack(subject))] + end + end + + let(:msgpack_json) { msgpack_jsons.first } let(:content) { msgpack_json["content"] } let(:meta) { content["meta"] } let(:metrics) { content["metrics"] } diff --git a/spec/support/tracer_helpers.rb b/spec/support/tracer_helpers.rb index a1ca5bd6..60148cae 100644 --- a/spec/support/tracer_helpers.rb +++ b/spec/support/tracer_helpers.rb @@ -52,6 +52,7 @@ def produce_test_trace( end def produce_test_session_trace( + tests_count: 1, framework: "rspec", operation: "rspec.example", test_name: "test_add", test_suite: "calculator_tests", service: "rspec-test-suite", result: "PASSED", exception: nil, @@ -71,15 +72,17 @@ def produce_test_session_trace( } ) - produce_test_trace( - framework: framework, operation: operation, - test_name: test_name, test_suite: test_suite, - # service is inherited from test_session - service: nil, - result: result, exception: exception, skip_reason: skip_reason, - start_time: start_time, duration_seconds: duration_seconds, - with_http_span: with_http_span - ) + tests_count.times do |num| + produce_test_trace( + framework: framework, operation: operation, + test_name: "#{test_name}.run.#{num}", test_suite: test_suite, + # service is inherited from test_session + service: nil, + result: result, exception: exception, skip_reason: skip_reason, + start_time: start_time, duration_seconds: duration_seconds, + with_http_span: with_http_span + ) + end set_result(test_session, result: result, exception: exception, skip_reason: skip_reason) test_session.finish @@ -97,7 +100,7 @@ def first_other_span spans.find { |span| !Datadog::CI::Ext::AppTypes::CI_SPAN_TYPES.include?(span.type) } end - # Returns spans and caches it (similar to +let(:spans)+). + # Returns traces and caches it (similar to +let(:traces)+). def traces @traces ||= fetch_traces end @@ -119,6 +122,11 @@ def trace end end + # returns trace associated with given span + def trace_for_span(span) + traces.find { |trace| trace.id == span.trace_id } + end + # Returns the only span in the current tracer writer. # # This method will not allow for ambiguous use, From d8a2db8e051306e5631ce66dffe268b05704dbb0 Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Tue, 28 Nov 2023 15:07:53 +0100 Subject: [PATCH 2/5] force creation of a new trace every time tracing a test --- lib/datadog/ci/recorder.rb | 16 ++++++++++ .../ci/test_visibility/serializers/test_v2.rb | 19 ++---------- lib/datadog/ci/test_visibility/transport.rb | 2 +- sig/datadog/ci/recorder.rbs | 2 ++ spec/datadog/ci/recorder_spec.rb | 29 +++++++++++++++---- .../test_visibility/serializers/span_spec.rb | 15 +++------- .../serializers/test_v1_spec.rb | 2 +- .../serializers/test_v2_spec.rb | 8 ++--- .../0/datadog/tracing/trace_digest.rbs | 27 +++++++++++++++++ .../rbs/ddtrace/0/datadog/tracing/utils.rbs | 25 ++++++++++++++++ 10 files changed, 105 insertions(+), 40 deletions(-) create mode 100644 vendor/rbs/ddtrace/0/datadog/tracing/trace_digest.rbs create mode 100644 vendor/rbs/ddtrace/0/datadog/tracing/utils.rbs diff --git a/lib/datadog/ci/recorder.rb b/lib/datadog/ci/recorder.rb index c163d88e..6396d167 100644 --- a/lib/datadog/ci/recorder.rb +++ b/lib/datadog/ci/recorder.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "datadog/tracing" +require "datadog/tracing/utils" require "rbconfig" @@ -64,6 +65,8 @@ def trace_test(test_name, service_name: nil, operation_name: "test", tags: {}, & service: service_name, span_type: Ext::AppTypes::TYPE_TEST } + # in order to + force_new_trace!(span_options) tags[Ext::Test::TAG_NAME] = test_name tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id if test_session @@ -136,6 +139,19 @@ def set_trace_origin(trace) trace.origin = Ext::Test::CONTEXT_ORIGIN if trace end + def force_new_trace!(span_options) + # if there is an active trace already (from session/module/suite) + # force creation of a new trace for the test by providing a new trace_id + # via continue_from parameter + current_trace = Datadog::Tracing.active_trace + if current_trace + digest = Datadog::Tracing::TraceDigest.new( + trace_id: Datadog::Tracing::Utils.next_id + ) + span_options[:continue_from] = digest + end + end + def build_test_session(tracer_span, tags) test_session = TestSession.new(tracer_span) set_initial_tags(test_session, tags) diff --git a/lib/datadog/ci/test_visibility/serializers/test_v2.rb b/lib/datadog/ci/test_visibility/serializers/test_v2.rb index 1a08c1d0..fe68e38e 100644 --- a/lib/datadog/ci/test_visibility/serializers/test_v2.rb +++ b/lib/datadog/ci/test_visibility/serializers/test_v2.rb @@ -8,26 +8,11 @@ module CI module TestVisibility module Serializers class TestV2 < TestV1 - CONTENT_FIELDS = [ - "trace_id", "span_id", - "name", "resource", "service", - "error", "start", "duration", - "meta", "metrics", "test_session_id", - "type" => "span_type" - ].freeze + CONTENT_FIELDS = (["test_session_id"] + TestV1::CONTENT_FIELDS).freeze CONTENT_MAP_SIZE = calculate_content_map_size(CONTENT_FIELDS) - REQUIRED_FIELDS = [ - "test_session_id", - "trace_id", - "span_id", - "error", - "name", - "resource", - "start", - "duration" - ].freeze + REQUIRED_FIELDS = (["test_session_id"] + TestV1::REQUIRED_FIELDS).freeze def content_fields CONTENT_FIELDS diff --git a/lib/datadog/ci/test_visibility/transport.rb b/lib/datadog/ci/test_visibility/transport.rb index 03b12fec..07480ac8 100644 --- a/lib/datadog/ci/test_visibility/transport.rb +++ b/lib/datadog/ci/test_visibility/transport.rb @@ -81,7 +81,7 @@ def encode_traces(traces) if spans.respond_to?(:filter_map) spans.filter_map { |span| encode_span(trace, span) } else - trace.spans.map { |span| encode_span(trace, span) }.reject(&:nil?) + spans.map { |span| encode_span(trace, span) }.reject(&:nil?) end end end diff --git a/sig/datadog/ci/recorder.rbs b/sig/datadog/ci/recorder.rbs index edb90fa7..a8da6f72 100644 --- a/sig/datadog/ci/recorder.rbs +++ b/sig/datadog/ci/recorder.rbs @@ -39,6 +39,8 @@ module Datadog def build_span: (Datadog::Tracing::SpanOperation tracer_span, Hash[untyped, untyped] tags) -> Datadog::CI::Span def set_initial_tags: (Datadog::CI::Span ci_span, Hash[untyped, untyped] tags) -> void + + def force_new_trace!: (Hash[untyped, untyped] span_options) -> void end end end diff --git a/spec/datadog/ci/recorder_spec.rb b/spec/datadog/ci/recorder_spec.rb index 58b186f2..f2287fcb 100644 --- a/spec/datadog/ci/recorder_spec.rb +++ b/spec/datadog/ci/recorder_spec.rb @@ -8,6 +8,9 @@ let(:environment_tags) { Datadog::CI::Ext::Environment.tags(ENV) } let(:test_suite_level_visibility_enabled) { true } + let(:trace_id) { 422 } + let(:fake_trace_digest) { Datadog::Tracing::TraceDigest.new(trace_id: trace_id) } + let(:ci_span) do spy("CI object spy") end @@ -16,6 +19,8 @@ before do allow(Datadog::Tracing).to receive(:active_trace).and_return(trace_op) + allow(Datadog::Tracing::Utils).to receive(:next_id).and_return(trace_id) + allow(Datadog::Tracing::TraceDigest).to receive(:new).with(trace_id: trace_id).and_return(fake_trace_digest) allow(trace_op).to receive(:origin=) end @@ -65,7 +70,8 @@ { span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, resource: test_name, - service: service + service: service, + continue_from: fake_trace_digest } ) @@ -105,7 +111,8 @@ { span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, resource: test_name, - service: service + service: service, + continue_from: fake_trace_digest } ) .and_return(span_op) @@ -140,7 +147,11 @@ context "when service name and tags are not given" do let(:expected_tags) do - {"test.framework" => "my framework", "test.name" => test_name, "_test.session_id" => test_session_id} + { + "test.framework" => "my framework", + "test.name" => test_name, + "_test.session_id" => test_session_id + } end subject(:trace) do @@ -159,7 +170,8 @@ { span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, resource: test_name, - service: test_session_service + service: test_session_service, + continue_from: fake_trace_digest } ) .and_return(span_op) @@ -176,7 +188,11 @@ context "when service name and tags are given" do let(:tags) { {"test.framework" => "special test framework"} } let(:expected_tags) do - {"test.framework" => "special test framework", "test.name" => test_name, "_test.session_id" => test_session_id} + { + "test.framework" => "special test framework", + "test.name" => test_name, + "_test.session_id" => test_session_id + } end subject(:trace) do @@ -196,7 +212,8 @@ { span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, resource: test_name, - service: service + service: service, + continue_from: fake_trace_digest } ) .and_return(span_op) diff --git a/spec/datadog/ci/test_visibility/serializers/span_spec.rb b/spec/datadog/ci/test_visibility/serializers/span_spec.rb index bddaa76e..7adc847f 100644 --- a/spec/datadog/ci/test_visibility/serializers/span_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/span_spec.rb @@ -7,15 +7,7 @@ end include_context "Test visibility event serialized" do - subject { described_class.new(trace_for_span(tracer_span), tracer_span) } - end - - let(:test_span) do - spans.find { |span| span.type == "test" } - end - - let(:tracer_span) do - spans.find { |span| span.type != "test" } + subject { described_class.new(trace_for_span(first_other_span), first_other_span) } end describe "#to_msgpack" do @@ -26,9 +18,10 @@ it "serializes test event to messagepack" do expect_event_header(type: "span") + expect(content).to include( { - "trace_id" => trace.id, + "trace_id" => first_test_span.trace_id, "span_id" => first_other_span.id, "parent_id" => first_test_span.id, "name" => "http-call", @@ -57,7 +50,7 @@ before do produce_test_trace(with_http_span: true) - tracer_span.name = nil + first_other_span.name = nil end it { is_expected.not_to be_valid } diff --git a/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb b/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb index 7232d9f6..b46c7a49 100644 --- a/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/test_v1_spec.rb @@ -21,7 +21,7 @@ expect(content).to include( { - "trace_id" => trace.id, + "trace_id" => span.trace_id, "span_id" => span.id, "name" => "rspec.test", "service" => "rspec-test-suite", diff --git a/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb b/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb index 28165b3c..0c1628f0 100644 --- a/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb +++ b/spec/datadog/ci/test_visibility/serializers/test_v2_spec.rb @@ -21,7 +21,7 @@ expect(content).to include( { - "trace_id" => trace.id, + "trace_id" => first_test_span.trace_id, "span_id" => first_test_span.id, "name" => "rspec.test", "service" => "rspec-test-suite", @@ -42,7 +42,7 @@ ) expect(meta["_test.session_id"]).to be_nil - expect(metrics).to eq({"memory_allocations" => 16}) + expect(metrics).to eq({"_dd.top_level" => 1, "memory_allocations" => 16}) end end @@ -70,9 +70,9 @@ end end - it "all tests have the same trace_id" do + it "all tests have different trace ids" do unique_trace_ids = msgpack_jsons.map { |msgpack_json| msgpack_json["content"]["trace_id"] }.uniq - expect(unique_trace_ids.size).to eq(1) + expect(unique_trace_ids.size).to eq(2) end end diff --git a/vendor/rbs/ddtrace/0/datadog/tracing/trace_digest.rbs b/vendor/rbs/ddtrace/0/datadog/tracing/trace_digest.rbs new file mode 100644 index 00000000..3975e905 --- /dev/null +++ b/vendor/rbs/ddtrace/0/datadog/tracing/trace_digest.rbs @@ -0,0 +1,27 @@ +module Datadog + module Tracing + class TraceDigest + attr_reader span_id: untyped + attr_reader span_name: untyped + attr_reader span_resource: untyped + attr_reader span_service: untyped + attr_reader span_type: untyped + attr_reader trace_distributed_tags: untyped + attr_reader trace_hostname: untyped + attr_reader trace_id: untyped + attr_reader trace_name: untyped + attr_reader trace_origin: untyped + attr_reader trace_process_id: untyped + attr_reader trace_resource: untyped + attr_reader trace_runtime_id: untyped + attr_reader trace_sampling_priority: untyped + attr_reader trace_service: untyped + attr_reader trace_distributed_id: untyped + attr_reader trace_flags: untyped + attr_reader trace_state: untyped + attr_reader trace_state_unknown_fields: untyped + + def initialize: (?span_id: untyped?, ?span_name: untyped?, ?span_resource: untyped?, ?span_service: untyped?, ?span_type: untyped?, ?trace_distributed_tags: untyped?, ?trace_hostname: untyped?, ?trace_id: untyped?, ?trace_name: untyped?, ?trace_origin: untyped?, ?trace_process_id: untyped?, ?trace_resource: untyped?, ?trace_runtime_id: untyped?, ?trace_sampling_priority: untyped?, ?trace_service: untyped?, ?trace_distributed_id: untyped?, ?trace_flags: untyped?, ?trace_state: untyped?, ?trace_state_unknown_fields: untyped?) -> void + end + end +end diff --git a/vendor/rbs/ddtrace/0/datadog/tracing/utils.rbs b/vendor/rbs/ddtrace/0/datadog/tracing/utils.rbs new file mode 100644 index 00000000..b20ac2db --- /dev/null +++ b/vendor/rbs/ddtrace/0/datadog/tracing/utils.rbs @@ -0,0 +1,25 @@ +module Datadog + module Tracing + module Utils + extend Datadog::Core::Utils::Forking + RUBY_MAX_ID: untyped + RUBY_ID_RANGE: ::Range[::Integer] + EXTERNAL_MAX_ID: untyped + def self.next_id: () -> untyped + + def self.id_rng: () -> untyped + + def self.reset!: () -> untyped + module TraceId + MAX: untyped + def self?.next_id: () -> untyped + + def self?.to_high_order: (untyped trace_id) -> untyped + + def self?.to_low_order: (untyped trace_id) -> untyped + + def self?.concatenate: (untyped high_order, untyped low_order) -> untyped + end + end + end +end From 0bdc8038779b60959885097907ae203a4d8f7d05 Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Tue, 28 Nov 2023 15:35:57 +0100 Subject: [PATCH 3/5] always pass continue_from option to the tracer with an empty digest --- lib/datadog/ci/recorder.rb | 23 +++++------------------ sig/datadog/ci/recorder.rbs | 2 -- spec/datadog/ci/recorder_spec.rb | 3 +-- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/lib/datadog/ci/recorder.rb b/lib/datadog/ci/recorder.rb index 6396d167..203e6687 100644 --- a/lib/datadog/ci/recorder.rb +++ b/lib/datadog/ci/recorder.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "datadog/tracing" -require "datadog/tracing/utils" +require "datadog/tracing/trace_digest" require "rbconfig" @@ -63,10 +63,10 @@ def trace_test(test_name, service_name: nil, operation_name: "test", tags: {}, & span_options = { resource: test_name, service: service_name, - span_type: Ext::AppTypes::TYPE_TEST + span_type: Ext::AppTypes::TYPE_TEST, + # this option is needed to force a new trace to be created + continue_from: Datadog::Tracing::TraceDigest.new } - # in order to - force_new_trace!(span_options) tags[Ext::Test::TAG_NAME] = test_name tags[Ext::Test::TAG_TEST_SESSION_ID] = test_session.id if test_session @@ -123,7 +123,7 @@ def active_test_session @global_context.active_test_session end - # TODO: does it make sense to have a paramter here? + # TODO: does it make sense to have a parameter here? def deactivate_test(test) @local_context.deactivate_test!(test) end @@ -139,19 +139,6 @@ def set_trace_origin(trace) trace.origin = Ext::Test::CONTEXT_ORIGIN if trace end - def force_new_trace!(span_options) - # if there is an active trace already (from session/module/suite) - # force creation of a new trace for the test by providing a new trace_id - # via continue_from parameter - current_trace = Datadog::Tracing.active_trace - if current_trace - digest = Datadog::Tracing::TraceDigest.new( - trace_id: Datadog::Tracing::Utils.next_id - ) - span_options[:continue_from] = digest - end - end - def build_test_session(tracer_span, tags) test_session = TestSession.new(tracer_span) set_initial_tags(test_session, tags) diff --git a/sig/datadog/ci/recorder.rbs b/sig/datadog/ci/recorder.rbs index a8da6f72..edb90fa7 100644 --- a/sig/datadog/ci/recorder.rbs +++ b/sig/datadog/ci/recorder.rbs @@ -39,8 +39,6 @@ module Datadog def build_span: (Datadog::Tracing::SpanOperation tracer_span, Hash[untyped, untyped] tags) -> Datadog::CI::Span def set_initial_tags: (Datadog::CI::Span ci_span, Hash[untyped, untyped] tags) -> void - - def force_new_trace!: (Hash[untyped, untyped] span_options) -> void end end end diff --git a/spec/datadog/ci/recorder_spec.rb b/spec/datadog/ci/recorder_spec.rb index f2287fcb..85db758a 100644 --- a/spec/datadog/ci/recorder_spec.rb +++ b/spec/datadog/ci/recorder_spec.rb @@ -19,8 +19,7 @@ before do allow(Datadog::Tracing).to receive(:active_trace).and_return(trace_op) - allow(Datadog::Tracing::Utils).to receive(:next_id).and_return(trace_id) - allow(Datadog::Tracing::TraceDigest).to receive(:new).with(trace_id: trace_id).and_return(fake_trace_digest) + allow(Datadog::Tracing::TraceDigest).to receive(:new).and_return(fake_trace_digest) allow(trace_op).to receive(:origin=) end From c095c589dc26b9c92e706b8c8c25c925f1e50b9f Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Tue, 28 Nov 2023 16:52:17 +0100 Subject: [PATCH 4/5] start a new, better specs for Datadog::CI::Recorder --- spec/datadog/ci/better_recorder_spec.rb | 219 ++++++++++++++++++++++++ spec/support/ci_mode_helpers.rb | 10 +- 2 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 spec/datadog/ci/better_recorder_spec.rb diff --git a/spec/datadog/ci/better_recorder_spec.rb b/spec/datadog/ci/better_recorder_spec.rb new file mode 100644 index 00000000..5886afe3 --- /dev/null +++ b/spec/datadog/ci/better_recorder_spec.rb @@ -0,0 +1,219 @@ +RSpec.describe Datadog::CI::Recorder do + subject(:recorder) { described_class.new } + shared_examples_for "trace with ciapp-test origin" do + let(:trace_under_test) { subject } + + it "trace origin is ciapp-test" do + expect(trace_under_test.origin).to eq(Datadog::CI::Ext::Test::CONTEXT_ORIGIN) + end + end + + shared_examples_for "span with environment tags" do + let(:environment_tags) { Datadog::CI::Ext::Environment.tags(ENV) } + let(:span_under_test) { subject } + + it "has all the environment tags" do + environment_tags.each do |key, value| + expect(span_under_test.get_tag(key)).to eq(value) + end + end + end + + shared_examples_for "span with default tags" do + let(:span_under_test) { subject } + + it "span.kind is equal to test" do + expect(span_under_test.get_tag(Datadog::CI::Ext::Test::TAG_SPAN_KIND)).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) + end + end + + shared_examples_for "span with runtime tags" do + let(:span_under_test) { subject } + + it "runtime tags are all set" do + [ + Datadog::CI::Ext::Test::TAG_OS_ARCHITECTURE, + Datadog::CI::Ext::Test::TAG_OS_PLATFORM, + Datadog::CI::Ext::Test::TAG_RUNTIME_NAME, + Datadog::CI::Ext::Test::TAG_RUNTIME_VERSION + ].each do |tag| + expect(span_under_test.get_tag(tag)).not_to be_nil + end + expect(span_under_test.get_tag(Datadog::CI::Ext::Test::TAG_COMMAND)).to eq(test_command) + end + end + + context "when test suite level visibility is disabled" do + include_context "CI mode activated" do + let(:experimental_test_suite_level_visibility_enabled) { false } + end + end + + context "when test suite level visibility is enabled" do + include_context "CI mode activated" + + describe "#trace" do + let(:span_type) { "step" } + let(:span_name) { "my test step" } + let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } + + context "when given a block" do + before do + recorder.trace(span_type, span_name, tags: tags) do |span| + # simulate some work + span.set_metric("my.metric", 42) + sleep(0.1) + end + end + subject { span } + + it "traces the block" do + expect(subject.resource).to eq(span_name) + expect(subject.span_type).to eq(span_type) + end + + it "sets the custom metric correctly" do + expect(subject.get_metric("my.metric")).to eq(42) + end + + it "sets the tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + end + + context "without a block" do + subject { recorder.trace("step", "my test step", tags: tags) } + + it "returns a new CI span" do + expect(subject).to be_kind_of(Datadog::CI::Span) + end + + it "sets the tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end + + it "sets correct resource and span type for the underlying tracer span" do + subject.finish + + expect(span.resource).to eq(span_name) + expect(span.span_type).to eq(span_type) + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + end + end + + describe "#trace_test" do + let(:test_name) { "my test" } + let(:test_service_name) { "my-service" } + let(:operation_name) { "my-operation" } + let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } + + context "without a block" do + subject do + recorder.trace_test(test_name, service_name: test_service_name, operation_name: operation_name, tags: tags) + end + + context "when there is no active test session" do + it "returns a new CI test span" do + expect(subject).to be_kind_of(Datadog::CI::Test) + expect(subject.name).to eq(test_name) + expect(subject.service).to eq(test_service_name) + expect(subject.tracer_span.name).to eq(operation_name) + expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) + end + + it "sets the provided tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end + + it "does not connect the test span to the test session" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to be_nil + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + end + + context "when there is an active test session" do + let(:test_session_tags) { {"test.framework_version" => "1.0", "my.session.tag" => "my_session_value"} } + let(:session_service_name) { "my-session-service" } + let(:test_service_name) { nil } + + let(:test_session) { recorder.start_test_session(service_name: session_service_name, tags: test_session_tags) } + + before do + test_session + end + + it "returns a new CI test span using service from the test session" do + expect(subject).to be_kind_of(Datadog::CI::Test) + expect(subject.name).to eq(test_name) + expect(subject.service).to eq(session_service_name) + end + + it "sets the provided tags correctly while inheriting some tags from the session" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("test.framework_version")).to eq("1.0") + expect(subject.get_tag("my.tag")).to eq("my_value") + expect(subject.get_tag("my.session.tag")).to be_nil + end + + it "connects the test span to the test session" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to eq(test_session.id.to_s) + end + + it "starts a new trace" do + expect(subject.tracer_span.trace_id).not_to eq(test_session.tracer_span.trace_id) + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + end + end + + context "when given a block" do + before do + recorder.trace_test( + test_name, + service_name: test_service_name, + operation_name: operation_name, + tags: tags + ) do |test_span| + # simulate some work + test_span.set_metric("my.metric", 42) + sleep(0.1) + end + end + subject { span } + + it "traces and finishes a test" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_NAME)).to eq(test_name) + expect(subject.service).to eq(test_service_name) + expect(subject.name).to eq(operation_name) + expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) + end + + it "sets the provided tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + end + end + end +end diff --git a/spec/support/ci_mode_helpers.rb b/spec/support/ci_mode_helpers.rb index 90739634..e274fd01 100644 --- a/spec/support/ci_mode_helpers.rb +++ b/spec/support/ci_mode_helpers.rb @@ -1,7 +1,9 @@ RSpec.shared_context "CI mode activated" do let(:test_command) { "command" } - let(:integration_name) { :override_me } + let(:integration_name) { :no_instrument } let(:integration_options) { {} } + let(:experimental_test_suite_level_visibility_enabled) { true } + let(:recorder) { Datadog.send(:components).ci_recorder } before do allow_any_instance_of(Datadog::Core::Remote::Negotiation).to( @@ -12,8 +14,10 @@ Datadog.configure do |c| c.ci.enabled = true - c.ci.experimental_test_suite_level_visibility_enabled = true - c.ci.instrument integration_name, integration_options + c.ci.experimental_test_suite_level_visibility_enabled = experimental_test_suite_level_visibility_enabled + unless integration_name == :no_instrument + c.ci.instrument integration_name, integration_options + end end end From a14f853800c9d7a47e2a915a31a9b0168580b3a7 Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Tue, 28 Nov 2023 16:54:18 +0100 Subject: [PATCH 5/5] test that trace has ciapp-test as origin --- spec/datadog/ci/better_recorder_spec.rb | 219 -------- spec/datadog/ci/recorder_spec.rb | 645 +++++++++++------------- 2 files changed, 304 insertions(+), 560 deletions(-) delete mode 100644 spec/datadog/ci/better_recorder_spec.rb diff --git a/spec/datadog/ci/better_recorder_spec.rb b/spec/datadog/ci/better_recorder_spec.rb deleted file mode 100644 index 5886afe3..00000000 --- a/spec/datadog/ci/better_recorder_spec.rb +++ /dev/null @@ -1,219 +0,0 @@ -RSpec.describe Datadog::CI::Recorder do - subject(:recorder) { described_class.new } - shared_examples_for "trace with ciapp-test origin" do - let(:trace_under_test) { subject } - - it "trace origin is ciapp-test" do - expect(trace_under_test.origin).to eq(Datadog::CI::Ext::Test::CONTEXT_ORIGIN) - end - end - - shared_examples_for "span with environment tags" do - let(:environment_tags) { Datadog::CI::Ext::Environment.tags(ENV) } - let(:span_under_test) { subject } - - it "has all the environment tags" do - environment_tags.each do |key, value| - expect(span_under_test.get_tag(key)).to eq(value) - end - end - end - - shared_examples_for "span with default tags" do - let(:span_under_test) { subject } - - it "span.kind is equal to test" do - expect(span_under_test.get_tag(Datadog::CI::Ext::Test::TAG_SPAN_KIND)).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) - end - end - - shared_examples_for "span with runtime tags" do - let(:span_under_test) { subject } - - it "runtime tags are all set" do - [ - Datadog::CI::Ext::Test::TAG_OS_ARCHITECTURE, - Datadog::CI::Ext::Test::TAG_OS_PLATFORM, - Datadog::CI::Ext::Test::TAG_RUNTIME_NAME, - Datadog::CI::Ext::Test::TAG_RUNTIME_VERSION - ].each do |tag| - expect(span_under_test.get_tag(tag)).not_to be_nil - end - expect(span_under_test.get_tag(Datadog::CI::Ext::Test::TAG_COMMAND)).to eq(test_command) - end - end - - context "when test suite level visibility is disabled" do - include_context "CI mode activated" do - let(:experimental_test_suite_level_visibility_enabled) { false } - end - end - - context "when test suite level visibility is enabled" do - include_context "CI mode activated" - - describe "#trace" do - let(:span_type) { "step" } - let(:span_name) { "my test step" } - let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } - - context "when given a block" do - before do - recorder.trace(span_type, span_name, tags: tags) do |span| - # simulate some work - span.set_metric("my.metric", 42) - sleep(0.1) - end - end - subject { span } - - it "traces the block" do - expect(subject.resource).to eq(span_name) - expect(subject.span_type).to eq(span_type) - end - - it "sets the custom metric correctly" do - expect(subject.get_metric("my.metric")).to eq(42) - end - - it "sets the tags correctly" do - expect(subject.get_tag("test.framework")).to eq("my-framework") - expect(subject.get_tag("my.tag")).to eq("my_value") - end - - it_behaves_like "span with environment tags" - it_behaves_like "span with default tags" - it_behaves_like "span with runtime tags" - end - - context "without a block" do - subject { recorder.trace("step", "my test step", tags: tags) } - - it "returns a new CI span" do - expect(subject).to be_kind_of(Datadog::CI::Span) - end - - it "sets the tags correctly" do - expect(subject.get_tag("test.framework")).to eq("my-framework") - expect(subject.get_tag("my.tag")).to eq("my_value") - end - - it "sets correct resource and span type for the underlying tracer span" do - subject.finish - - expect(span.resource).to eq(span_name) - expect(span.span_type).to eq(span_type) - end - - it_behaves_like "span with environment tags" - it_behaves_like "span with default tags" - it_behaves_like "span with runtime tags" - end - end - - describe "#trace_test" do - let(:test_name) { "my test" } - let(:test_service_name) { "my-service" } - let(:operation_name) { "my-operation" } - let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } - - context "without a block" do - subject do - recorder.trace_test(test_name, service_name: test_service_name, operation_name: operation_name, tags: tags) - end - - context "when there is no active test session" do - it "returns a new CI test span" do - expect(subject).to be_kind_of(Datadog::CI::Test) - expect(subject.name).to eq(test_name) - expect(subject.service).to eq(test_service_name) - expect(subject.tracer_span.name).to eq(operation_name) - expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) - end - - it "sets the provided tags correctly" do - expect(subject.get_tag("test.framework")).to eq("my-framework") - expect(subject.get_tag("my.tag")).to eq("my_value") - end - - it "does not connect the test span to the test session" do - expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to be_nil - end - - it_behaves_like "span with environment tags" - it_behaves_like "span with default tags" - it_behaves_like "span with runtime tags" - end - - context "when there is an active test session" do - let(:test_session_tags) { {"test.framework_version" => "1.0", "my.session.tag" => "my_session_value"} } - let(:session_service_name) { "my-session-service" } - let(:test_service_name) { nil } - - let(:test_session) { recorder.start_test_session(service_name: session_service_name, tags: test_session_tags) } - - before do - test_session - end - - it "returns a new CI test span using service from the test session" do - expect(subject).to be_kind_of(Datadog::CI::Test) - expect(subject.name).to eq(test_name) - expect(subject.service).to eq(session_service_name) - end - - it "sets the provided tags correctly while inheriting some tags from the session" do - expect(subject.get_tag("test.framework")).to eq("my-framework") - expect(subject.get_tag("test.framework_version")).to eq("1.0") - expect(subject.get_tag("my.tag")).to eq("my_value") - expect(subject.get_tag("my.session.tag")).to be_nil - end - - it "connects the test span to the test session" do - expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to eq(test_session.id.to_s) - end - - it "starts a new trace" do - expect(subject.tracer_span.trace_id).not_to eq(test_session.tracer_span.trace_id) - end - - it_behaves_like "span with environment tags" - it_behaves_like "span with default tags" - it_behaves_like "span with runtime tags" - end - end - - context "when given a block" do - before do - recorder.trace_test( - test_name, - service_name: test_service_name, - operation_name: operation_name, - tags: tags - ) do |test_span| - # simulate some work - test_span.set_metric("my.metric", 42) - sleep(0.1) - end - end - subject { span } - - it "traces and finishes a test" do - expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_NAME)).to eq(test_name) - expect(subject.service).to eq(test_service_name) - expect(subject.name).to eq(operation_name) - expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) - end - - it "sets the provided tags correctly" do - expect(subject.get_tag("test.framework")).to eq("my-framework") - expect(subject.get_tag("my.tag")).to eq("my_value") - end - - it_behaves_like "span with environment tags" - it_behaves_like "span with default tags" - it_behaves_like "span with runtime tags" - end - end - end -end diff --git a/spec/datadog/ci/recorder_spec.rb b/spec/datadog/ci/recorder_spec.rb index 85db758a..cd8a3304 100644 --- a/spec/datadog/ci/recorder_spec.rb +++ b/spec/datadog/ci/recorder_spec.rb @@ -1,436 +1,399 @@ RSpec.describe Datadog::CI::Recorder do - let(:trace_op) { instance_double(Datadog::Tracing::TraceOperation) } - let(:service) { "service" } - let(:operation_name) { "span name" } - let(:test_name) { "test name" } - let(:tags) { {} } - let(:expected_tags) { {} } - let(:environment_tags) { Datadog::CI::Ext::Environment.tags(ENV) } - let(:test_suite_level_visibility_enabled) { true } - - let(:trace_id) { 422 } - let(:fake_trace_digest) { Datadog::Tracing::TraceDigest.new(trace_id: trace_id) } - - let(:ci_span) do - spy("CI object spy") + shared_examples_for "trace with ciapp-test origin" do + let(:trace_under_test) { subject } + + it "trace origin is ciapp-test" do + expect(trace_under_test.origin).to eq(Datadog::CI::Ext::Test::CONTEXT_ORIGIN) + end end - subject(:recorder) { described_class.new(test_suite_level_visibility_enabled: test_suite_level_visibility_enabled) } + shared_examples_for "span with environment tags" do + let(:environment_tags) { Datadog::CI::Ext::Environment.tags(ENV) } + let(:span_under_test) { subject } - before do - allow(Datadog::Tracing).to receive(:active_trace).and_return(trace_op) - allow(Datadog::Tracing::TraceDigest).to receive(:new).and_return(fake_trace_digest) - allow(trace_op).to receive(:origin=) + it "has all the environment tags" do + environment_tags.each do |key, value| + expect(span_under_test.get_tag(key)).to eq(value) + end + end end - shared_examples_for "internal tracing context" do - it do - expect(trace_op) - .to have_received(:origin=) - .with(Datadog::CI::Ext::Test::CONTEXT_ORIGIN) + shared_examples_for "span with default tags" do + let(:span_under_test) { subject } + + it "span.kind is equal to test" do + expect( + span_under_test.get_tag(Datadog::CI::Ext::Test::TAG_SPAN_KIND) + ).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) end end - shared_examples_for "initialize ci span with tags" do - it do - expect(ci_span).to have_received(:set_default_tags) - expect(ci_span).to have_received(:set_environment_runtime_tags) - expect(ci_span).to have_received(:set_tags).with(expected_tags) - expect(ci_span).to have_received(:set_tags).with(environment_tags) + shared_examples_for "span with runtime tags" do + let(:span_under_test) { subject } + + it "runtime tags are all set" do + [ + Datadog::CI::Ext::Test::TAG_OS_ARCHITECTURE, + Datadog::CI::Ext::Test::TAG_OS_PLATFORM, + Datadog::CI::Ext::Test::TAG_RUNTIME_NAME, + Datadog::CI::Ext::Test::TAG_RUNTIME_VERSION + ].each do |tag| + expect(span_under_test.get_tag(tag)).not_to be_nil + end + expect(span_under_test.get_tag(Datadog::CI::Ext::Test::TAG_COMMAND)).to eq(test_command) end end - describe "#trace_test" do - let(:expected_tags) { {"test.name" => test_name} } - - context "when given a block" do - subject(:trace) do - recorder.trace_test( - test_name, - service_name: service, - operation_name: operation_name, - tags: tags, - &block - ) - end + context "when test suite level visibility is disabled" do + include_context "CI mode activated" do + let(:experimental_test_suite_level_visibility_enabled) { false } + end - let(:span_op) { Datadog::Tracing::SpanOperation.new(operation_name) } - let(:block) { proc { |s| block_spy.call(s) } } - let(:block_result) { double("result") } - let(:block_spy) { spy("block") } - - before do - allow(block_spy).to receive(:call).and_return(block_result) - - allow(Datadog::Tracing) - .to receive(:trace) do |trace_span_name, trace_span_options, &trace_block| - expect(trace_span_name).to be(operation_name) - expect(trace_span_options).to eq( - { - span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, - resource: test_name, - service: service, - continue_from: fake_trace_digest - } - ) - - allow(Datadog::CI::Test).to receive(:new).with(span_op).and_return(ci_span) - - trace_block.call(span_op, trace_op) - end + describe "#trace_test_session" do + let(:service_name) { "my-service" } + let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } - trace - end + subject { recorder.start_test_session(service_name: service_name, tags: tags) } - it_behaves_like "internal tracing context" - it_behaves_like "initialize ci span with tags" + it { is_expected.to be_nil } - it { expect(block_spy).to have_received(:call).with(ci_span) } - it { is_expected.to be(block_result) } + it "does not activate session" do + expect(recorder.active_test_session).to be_nil + end end + end - context "when not given a block" do - let(:span_op) { Datadog::Tracing::SpanOperation.new(operation_name) } + context "when test suite level visibility is enabled" do + include_context "CI mode activated" - context "without active test session" do - subject(:trace) do - recorder.trace_test( - test_name, - service_name: service, - operation_name: operation_name, - tags: tags - ) - end + describe "#trace" do + let(:span_type) { "step" } + let(:span_name) { "my test step" } + let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } + context "when given a block" do before do - allow(Datadog::Tracing) - .to receive(:trace) - .with( - operation_name, - { - span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, - resource: test_name, - service: service, - continue_from: fake_trace_digest - } - ) - .and_return(span_op) - - allow(Datadog::CI::Test).to receive(:new).with(span_op).and_return(ci_span) + recorder.trace(span_type, span_name, tags: tags) do |span| + # simulate some work + span.set_metric("my.metric", 42) + sleep(0.1) + end + end + subject { span } - trace + it "traces the block" do + expect(subject.resource).to eq(span_name) + expect(subject.span_type).to eq(span_type) + end + + it "sets the custom metric correctly" do + expect(subject.get_metric("my.metric")).to eq(42) end - it_behaves_like "internal tracing context" - it_behaves_like "initialize ci span with tags" - it { is_expected.to be(ci_span) } + it "sets the tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" end - context "when test session is active" do - let(:test_session_id) { 42 } - let(:inheritable_tags) { {"test.framework" => "my framework"} } - - let(:test_session_service) { "my-test-service" } - let(:test_session) do - instance_double( - Datadog::CI::TestSession, - service: test_session_service, - inheritable_tags: inheritable_tags, - id: test_session_id - ) + context "without a block" do + subject { recorder.trace("step", "my test step", tags: tags) } + + it "returns a new CI span" do + expect(subject).to be_kind_of(Datadog::CI::Span) end - before do - allow(recorder).to receive(:active_test_session).and_return(test_session) + it "sets the tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") end - context "when service name and tags are not given" do - let(:expected_tags) do - { - "test.framework" => "my framework", - "test.name" => test_name, - "_test.session_id" => test_session_id - } - end + it "sets correct resource and span type for the underlying tracer span" do + subject.finish + + expect(span.resource).to eq(span_name) + expect(span.span_type).to eq(span_type) + end - subject(:trace) do - recorder.trace_test( - test_name, - operation_name: operation_name, - tags: tags - ) + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + end + end + + describe "#trace_test" do + let(:test_name) { "my test" } + let(:test_service_name) { "my-service" } + let(:operation_name) { "my-operation" } + let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } + + context "without a block" do + subject do + recorder.trace_test(test_name, service_name: test_service_name, operation_name: operation_name, tags: tags) + end + + context "when there is no active test session" do + it "returns a new CI test span" do + expect(subject).to be_kind_of(Datadog::CI::Test) + expect(subject.name).to eq(test_name) + expect(subject.service).to eq(test_service_name) + expect(subject.tracer_span.name).to eq(operation_name) + expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) end - before do - allow(Datadog::Tracing) - .to receive(:trace) - .with( - operation_name, - { - span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, - resource: test_name, - service: test_session_service, - continue_from: fake_trace_digest - } - ) - .and_return(span_op) - - allow(Datadog::CI::Test).to receive(:new).with(span_op).and_return(ci_span) + it "sets the provided tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end - trace + it "does not connect the test span to the test session" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to be_nil end - it_behaves_like "initialize ci span with tags" - it { is_expected.to be(ci_span) } + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + it_behaves_like "trace with ciapp-test origin" do + let(:trace_under_test) do + subject.finish + + trace + end + end end - context "when service name and tags are given" do - let(:tags) { {"test.framework" => "special test framework"} } - let(:expected_tags) do - { - "test.framework" => "special test framework", - "test.name" => test_name, - "_test.session_id" => test_session_id - } + context "when there is an active test session" do + let(:test_session_tags) { {"test.framework_version" => "1.0", "my.session.tag" => "my_session_value"} } + let(:session_service_name) { "my-session-service" } + let(:test_service_name) { nil } + + let(:test_session) { recorder.start_test_session(service_name: session_service_name, tags: test_session_tags) } + + before do + test_session end - subject(:trace) do - recorder.trace_test( - test_name, - service_name: service, - operation_name: operation_name, - tags: tags - ) + it "returns a new CI test span using service from the test session" do + expect(subject).to be_kind_of(Datadog::CI::Test) + expect(subject.name).to eq(test_name) + expect(subject.service).to eq(session_service_name) end - before do - allow(Datadog::Tracing) - .to receive(:trace) - .with( - operation_name, - { - span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST, - resource: test_name, - service: service, - continue_from: fake_trace_digest - } - ) - .and_return(span_op) - - allow(Datadog::CI::Test).to receive(:new).with(span_op).and_return(ci_span) + it "sets the provided tags correctly while inheriting some tags from the session" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("test.framework_version")).to eq("1.0") + expect(subject.get_tag("my.tag")).to eq("my_value") + expect(subject.get_tag("my.session.tag")).to be_nil + end - trace + it "connects the test span to the test session" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to eq(test_session.id.to_s) end - it_behaves_like "initialize ci span with tags" - it { is_expected.to be(ci_span) } - end - end - end - end + it "starts a new trace" do + expect(subject.tracer_span.trace_id).not_to eq(test_session.tracer_span.trace_id) + end + + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" - describe "#trace" do - let(:tags) { {"my_tag" => "my_value"} } - let(:expected_tags) { {"my_tag" => "my_value"} } - let(:span_type) { "step" } - let(:span_name) { "span name" } - - context "when given a block" do - subject(:trace) do - recorder.trace( - span_type, - span_name, - tags: tags, - &block - ) + it_behaves_like "trace with ciapp-test origin" do + let(:trace_under_test) do + subject.finish + + trace + end + end + end end - let(:span_op) { Datadog::Tracing::SpanOperation.new(span_name) } - let(:block) { proc { |s| block_spy.call(s) } } - let(:block_result) { double("result") } - let(:block_spy) { spy("block") } - - before do - allow(block_spy).to receive(:call).and_return(block_result) - - allow(Datadog::Tracing) - .to receive(:trace) do |trace_span_name, trace_span_options, &trace_block| - expect(trace_span_name).to be(span_name) - expect(trace_span_options).to eq( - { - span_type: span_type, - resource: span_name - } - ) - trace_block.call(span_op, trace_op) + context "when given a block" do + before do + recorder.trace_test( + test_name, + service_name: test_service_name, + operation_name: operation_name, + tags: tags + ) do |test_span| + # simulate some work + test_span.set_metric("my.metric", 42) + sleep(0.1) end + end + subject { span } - allow(Datadog::CI::Span).to receive(:new).with(span_op).and_return(ci_span) + it "traces and finishes a test" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_NAME)).to eq(test_name) + expect(subject.service).to eq(test_service_name) + expect(subject.name).to eq(operation_name) + expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) + end - trace - end + it "sets the provided tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") + end - it_behaves_like "initialize ci span with tags" - it { expect(block_spy).to have_received(:call).with(ci_span) } - it { is_expected.to be(block_result) } + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + it_behaves_like "trace with ciapp-test origin" do + let(:trace_under_test) do + trace + end + end + end end - context "when not given a block" do - subject(:trace) do - recorder.trace( - span_type, - span_name, - tags: tags - ) - end + describe "#start_test_session" do + let(:service_name) { "my-service" } + let(:tags) { {"test.framework" => "my-framework", "my.tag" => "my_value"} } - let(:span_op) { Datadog::Tracing::SpanOperation.new(span_name) } + subject { recorder.start_test_session(service_name: service_name, tags: tags) } - before do - allow(Datadog::Tracing) - .to receive(:trace) - .with( - span_name, - { - span_type: span_type, - resource: span_name - } - ) - .and_return(span_op) + it "returns a new CI test_session span" do + expect(subject).to be_kind_of(Datadog::CI::TestSession) + expect(subject.name).to eq("test.session") + expect(subject.service).to eq(service_name) + expect(subject.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST_SESSION) + end - allow(Datadog::CI::Span).to receive(:new).with(span_op).and_return(ci_span) + it "sets the test session id" do + expect(subject.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to eq(subject.id.to_s) + end - trace + it "sets the provided tags correctly" do + expect(subject.get_tag("test.framework")).to eq("my-framework") + expect(subject.get_tag("my.tag")).to eq("my_value") end - it_behaves_like "initialize ci span with tags" - it { is_expected.to be(ci_span) } + it_behaves_like "span with environment tags" + it_behaves_like "span with default tags" + it_behaves_like "span with runtime tags" + it_behaves_like "trace with ciapp-test origin" do + let(:trace_under_test) do + subject.finish + + trace + end + end end - end - describe "#active_test" do - subject(:active_test) { recorder.active_test } + describe "#active_test_session" do + subject { recorder.active_test_session } + context "when there is no active test session" do + it { is_expected.to be_nil } + end + + context "when test session is started" do + let(:test_session) { recorder.start_test_session } + before do + test_session + end - let(:ci_test) do - recorder.trace_test( - test_name, - service_name: service, - operation_name: operation_name, - tags: tags - ) + it "returns the active test session" do + expect(subject).to be(test_session) + end + end end - before { ci_test } + describe "#active_test" do + subject { recorder.active_test } - it { is_expected.to be(ci_test) } - end + context "when there is no active test" do + it { is_expected.to be_nil } + end - describe "#deactivate_test" do - subject(:deactivate_test) { recorder.deactivate_test(ci_test) } + context "when test is started" do + let(:ci_test) { recorder.trace_test("my test") } - let(:ci_test) do - recorder.trace_test( - test_name, - service_name: service, - operation_name: operation_name, - tags: tags - ) + before do + ci_test + end + + it "returns the active test" do + expect(subject).to be(ci_test) + end + end end - before { deactivate_test } + describe "#active_span" do + subject { recorder.active_span } - it { expect(recorder.active_test).to be_nil } - end + context "when there is no active span" do + it { is_expected.to be_nil } + end - describe "#active_span" do - subject(:active_span) { recorder.active_span } + context "when span is started" do + let(:ci_span) { recorder.trace("step", "my test step") } - context "when there is active span in tracing context" do - let(:span_op) { Datadog::Tracing::SpanOperation.new(operation_name) } - let(:ci_span) { instance_double(Datadog::CI::Span) } + before do + ci_span + end - before do - allow(Datadog::Tracing).to receive(:active_span).and_return(span_op) - allow(Datadog::CI::Span).to receive(:new).with(span_op).and_return(ci_span) + it "returns a wrapper around the active tracer span" do + expect(subject).to be_kind_of(Datadog::CI::Span) + expect(subject.tracer_span.name).to eq("my test step") + end end - - it { is_expected.to be(ci_span) } end - context "when there is no active span in tracing context" do - before { allow(Datadog::Tracing).to receive(:active_span).and_return(nil) } + describe "#deactivate_test" do + subject { recorder.deactivate_test(ci_test) } - it { is_expected.to be_nil } - end - end + context "when there is no active test" do + let(:ci_test) { Datadog::CI::Test.new(double("tracer span")) } - describe "#start_test_session" do - context "when test suite level visibility is enabled" do - subject(:start_test_session) { recorder.start_test_session(service_name: service) } - - let(:session_operation_name) { "test.session" } - let(:span_op) { Datadog::Tracing::SpanOperation.new(session_operation_name) } - let(:expected_tags) { {"_test.session_id" => span_op.id} } - - before do - allow(Datadog::Tracing) - .to receive(:trace) - .with( - session_operation_name, - { - span_type: Datadog::CI::Ext::AppTypes::TYPE_TEST_SESSION, - service: service - } - ) - .and_return(span_op) - - allow(Datadog::CI::TestSession).to receive(:new).with(span_op).and_return(ci_span) - - start_test_session + it { is_expected.to be_nil } end - it_behaves_like "internal tracing context" - it_behaves_like "initialize ci span with tags" - - it { is_expected.to be(ci_span) } - end - - context "when test suite level visibility is disabled" do - subject(:start_test_session) { recorder.start_test_session(service_name: service) } + context "when deactivating the currently active test" do + let(:ci_test) { recorder.trace_test("my test") } - let(:test_suite_level_visibility_enabled) { false } + it "deactivates the test" do + subject - before { start_test_session } + expect(recorder.active_test).to be_nil + end + end - it { is_expected.to be_nil } - end - end + context "when deactivating a different test from the one that is running right now" do + let(:ci_test) { Datadog::CI::Test.new(double("tracer span", get_tag: "wrong test")) } - describe "#active_test_session" do - subject(:active_test_session) { recorder.active_test_session } + before do + recorder.trace_test("my test") + end - let(:ci_session) do - recorder.start_test_session(service_name: service) + it "raises an error" do + expect { subject }.to raise_error(/Trying to deactivate test Datadog::CI::Test\(name:wrong test/) + expect(recorder.active_test).not_to be_nil + end + end end - before { ci_session } - - it { is_expected.to be(ci_session) } - end + describe "#deactivate_test_session" do + subject { recorder.deactivate_test_session } - describe "#deactivate_test_session" do - subject(:deactivate_test_session) { recorder.deactivate_test_session } + context "when there is no active test session" do + it { is_expected.to be_nil } + end - let(:ci_session) do - recorder.start_test_session(service_name: service) - end + context "when deactivating the currently active test session" do + before do + recorder.start_test_session + end - before do - ci_session + it "deactivates the test session" do + subject - deactivate_test_session + expect(recorder.active_test_session).to be_nil + end + end end - - it { expect(recorder.active_test_session).to be_nil } end end