From 4e70b4be8d82c5381e339c1102915d2c665b376e Mon Sep 17 00:00:00 2001 From: Andrey Marchenko Date: Tue, 7 Nov 2023 15:21:12 +0100 Subject: [PATCH] implement local context to keep track of current test being executed --- lib/datadog/ci.rb | 13 ++ lib/datadog/ci/context.rb | 25 ---- lib/datadog/ci/context/local.rb | 59 ++++++++ lib/datadog/ci/context_provider.rb | 62 -------- lib/datadog/ci/contrib/cucumber/formatter.rb | 26 ++-- lib/datadog/ci/contrib/minitest/hooks.rb | 8 +- lib/datadog/ci/recorder.rb | 56 ++++++-- lib/datadog/ci/span.rb | 4 + lib/datadog/ci/test.rb | 15 ++ sig/datadog/ci.rbs | 10 +- sig/datadog/ci/context.rbs | 14 -- sig/datadog/ci/context/local.rbs | 26 ++++ sig/datadog/ci/context_provider.rbs | 24 ---- sig/datadog/ci/recorder.rbs | 9 +- sig/datadog/ci/span.rbs | 8 +- sig/datadog/ci/test.rbs | 7 + spec/datadog/ci/context/local_spec.rb | 134 ++++++++++++++++++ spec/datadog/ci/recorder_spec.rb | 63 +++++++- spec/datadog/ci/span_spec.rb | 9 ++ spec/datadog/ci/test_spec.rb | 16 +++ spec/datadog/ci_spec.rb | 55 +++++-- spec/support/tracer_helpers.rb | 4 +- .../ddtrace/0/datadog/core/utils/sequence.rbs | 11 ++ vendor/rbs/ddtrace/0/datadog/tracing.rbs | 1 + 24 files changed, 481 insertions(+), 178 deletions(-) delete mode 100644 lib/datadog/ci/context.rb create mode 100644 lib/datadog/ci/context/local.rb delete mode 100644 lib/datadog/ci/context_provider.rb create mode 100644 lib/datadog/ci/test.rb delete mode 100644 sig/datadog/ci/context.rbs create mode 100644 sig/datadog/ci/context/local.rbs delete mode 100644 sig/datadog/ci/context_provider.rbs create mode 100644 sig/datadog/ci/test.rbs create mode 100644 spec/datadog/ci/context/local_spec.rb create mode 100644 spec/datadog/ci/test_spec.rb create mode 100644 vendor/rbs/ddtrace/0/datadog/core/utils/sequence.rbs diff --git a/lib/datadog/ci.rb b/lib/datadog/ci.rb index a99b279d..55c14c74 100644 --- a/lib/datadog/ci.rb +++ b/lib/datadog/ci.rb @@ -28,6 +28,19 @@ def trace(span_type, span_name, tags: {}, &block) recorder.trace(span_type, span_name, tags: tags, &block) end + def active_span(span_type) + span = recorder.active_span + span if span && span.span_type == span_type + end + + def active_test + recorder.active_test + end + + def deactivate_test(test) + recorder.deactivate_test(test) + end + private def components diff --git a/lib/datadog/ci/context.rb b/lib/datadog/ci/context.rb deleted file mode 100644 index 93a47158..00000000 --- a/lib/datadog/ci/context.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require "datadog/core/utils/forking" - -module Datadog - module CI - class Context - include Core::Utils::Forking - - attr_reader \ - :active_test - - def initialize(test: nil) - @active_test = test - end - - # Creates a copy of the context, when forked. - def fork_clone - # forked_session = @active_session && @active_session.fork_clone - # do not preserves the active test across forks - self.class.new - end - end - end -end diff --git a/lib/datadog/ci/context/local.rb b/lib/datadog/ci/context/local.rb new file mode 100644 index 00000000..5a979a5d --- /dev/null +++ b/lib/datadog/ci/context/local.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "datadog/core/utils/sequence" + +module Datadog + module CI + module Context + class Local + def initialize + @key = "datadog_ci_active_test_#{Local.next_instance_id}" + end + + def activate_test!(test) + raise "Nested tests are not supported. Currently active test: #{active_test}" unless active_test.nil? + + if block_given? + begin + self.active_test = test + yield + ensure + self.active_test = nil + end + else + self.active_test = test + end + end + + def deactivate_test!(test) + return if active_test.nil? + + if active_test == test + self.active_test = nil + else + raise "Trying to deactivate test #{test}, but currently active test is #{active_test}" + end + end + + def active_test + Thread.current[@key] + end + + UNIQUE_INSTANCE_MUTEX = Mutex.new + UNIQUE_INSTANCE_GENERATOR = Datadog::Core::Utils::Sequence.new + + private_constant :UNIQUE_INSTANCE_MUTEX, :UNIQUE_INSTANCE_GENERATOR + + def self.next_instance_id + UNIQUE_INSTANCE_MUTEX.synchronize { UNIQUE_INSTANCE_GENERATOR.next } + end + + private + + def active_test=(test) + Thread.current[@key] = test + end + end + end + end +end diff --git a/lib/datadog/ci/context_provider.rb b/lib/datadog/ci/context_provider.rb deleted file mode 100644 index 2ddafc14..00000000 --- a/lib/datadog/ci/context_provider.rb +++ /dev/null @@ -1,62 +0,0 @@ -require "datadog/core/utils/sequence" - -require_relative "context" - -module Datadog - module CI - # Provider is a default context provider that retrieves - # all contexts from the current fiber-local storage. It is suitable for - # synchronous programming. - # - # @see https://ruby-doc.org/core-3.1.2/Thread.html#method-i-5B-5D Thread attributes are fiber-local - class ContextProvider - # Initializes the default context provider with a fiber-bound context. - def initialize - @context = FiberLocalContext.new - end - - # Sets the current context. - def context=(ctx) - @context.local = ctx - end - - # Return the local context. - def context(key = nil) - current_context = key.nil? ? @context.local : @context.local(key) - - current_context.after_fork! do - current_context = self.context = current_context.fork_clone - end - - current_context - end - end - - class FiberLocalContext - def initialize - @key = "datadog_ci_context_#{FiberLocalContext.next_instance_id}".to_sym - - self.local = Context.new - end - - # Override the fiber-local context with a new context. - def local=(ctx) - Thread.current[@key] = ctx - end - - # Return the fiber-local context. - def local(storage = Thread.current) - storage[@key] ||= Context.new - end - - UNIQUE_INSTANCE_MUTEX = Mutex.new - UNIQUE_INSTANCE_GENERATOR = Datadog::Core::Utils::Sequence.new - - private_constant :UNIQUE_INSTANCE_MUTEX, :UNIQUE_INSTANCE_GENERATOR - - def self.next_instance_id - UNIQUE_INSTANCE_MUTEX.synchronize { UNIQUE_INSTANCE_GENERATOR.next } - end - end - end -end diff --git a/lib/datadog/ci/contrib/cucumber/formatter.rb b/lib/datadog/ci/contrib/cucumber/formatter.rb index 1506a705..2786bfd3 100644 --- a/lib/datadog/ci/contrib/cucumber/formatter.rb +++ b/lib/datadog/ci/contrib/cucumber/formatter.rb @@ -27,7 +27,7 @@ def bind_events(config) end def on_test_case_started(event) - @current_feature_span = CI.start_test( + CI.start_test( event.test_case.name, tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, @@ -41,35 +41,37 @@ def on_test_case_started(event) end def on_test_case_finished(event) - return if @current_feature_span.nil? + test_span = CI.active_test + return if test_span.nil? if event.result.skipped? - @current_feature_span.skipped! + test_span.skipped! elsif event.result.ok? - @current_feature_span.passed! + test_span.passed! elsif event.result.failed? - @current_feature_span.failed! + test_span.failed! end - @current_feature_span.finish + test_span.finish end def on_test_step_started(event) - @current_step_span = CI.trace(Ext::STEP_SPAN_TYPE, event.test_step.to_s) + CI.trace(Ext::STEP_SPAN_TYPE, event.test_step.to_s) end def on_test_step_finished(event) - return if @current_step_span.nil? + current_step_span = CI.active_span(Ext::STEP_SPAN_TYPE) + return if current_step_span.nil? if event.result.skipped? - @current_step_span.skipped! + current_step_span.skipped! elsif event.result.ok? - @current_step_span.passed! + current_step_span.passed! elsif event.result.failed? - @current_step_span.failed!(exception: event.result.exception) + current_step_span.failed!(exception: event.result.exception) end - @current_step_span.finish + current_step_span.finish end private diff --git a/lib/datadog/ci/contrib/minitest/hooks.rb b/lib/datadog/ci/contrib/minitest/hooks.rb index 9506e5e0..b7c7cf64 100644 --- a/lib/datadog/ci/contrib/minitest/hooks.rb +++ b/lib/datadog/ci/contrib/minitest/hooks.rb @@ -18,7 +18,7 @@ def before_setup path, = method(name).source_location test_suite = Pathname.new(path.to_s).relative_path_from(Pathname.pwd).to_s - test_span = CI.start_test( + CI.start_test( test_name, tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, @@ -29,16 +29,12 @@ def before_setup service_name: configuration[:service_name], operation_name: configuration[:operation_name] ) - - Thread.current[:_datadog_test_span] = test_span end def after_teardown - test_span = Thread.current[:_datadog_test_span] + test_span = CI.active_test return super unless test_span - Thread.current[:_datadog_test_span] = nil - case result_code when "." test_span.passed! diff --git a/lib/datadog/ci/recorder.rb b/lib/datadog/ci/recorder.rb index 9b79a965..c99310cf 100644 --- a/lib/datadog/ci/recorder.rb +++ b/lib/datadog/ci/recorder.rb @@ -8,7 +8,10 @@ require_relative "ext/test" require_relative "ext/environment" +require_relative "context/local" + require_relative "span" +require_relative "test" module Datadog module CI @@ -18,6 +21,7 @@ class Recorder def initialize @environment_tags = Ext::Environment.tags(ENV) + @local_context = Context::Local.new end # Creates a new span for a CI test @@ -31,7 +35,32 @@ def trace_test(test_name, service_name: nil, operation_name: "test", tags: {}, & tags[Ext::Test::TAG_NAME] = test_name tags.merge!(environment_tags) - create_datadog_span(operation_name, span_options: span_options, tags: tags, &block) + if block + Datadog::Tracing.trace(operation_name, **span_options) do |tracer_span, trace| + set_internal_tracing_context!(trace, tracer_span) + + test = Test.new(tracer_span, tags) + # test.set_default_tags! + # test.set_tags!(environment_tags) + # test.set_tags!(tags) + + @local_context.activate_test!(test) do + block.call(test) + end + end + else + tracer_span = Datadog::Tracing.trace(operation_name, **span_options) + trace = Datadog::Tracing.active_trace + + set_internal_tracing_context!(trace, tracer_span) + test = Test.new(tracer_span, tags) + # test.set_default_tags! + # test.set_tags!(environment_tags) + # test.set_tags!(tags) + + @local_context.activate_test!(test) + test + end end def trace(span_type, span_name, tags: {}, &block) @@ -40,26 +69,33 @@ def trace(span_type, span_name, tags: {}, &block) span_type: span_type } - create_datadog_span(span_name, span_options: span_options, tags: tags, &block) - end - - private - - def create_datadog_span(span_name, span_options: {}, tags: {}, &block) + # create_datadog_span(span_name, span_options: span_options, tags: tags, &block) if block Datadog::Tracing.trace(span_name, **span_options) do |tracer_span, trace| - set_internal_tracing_context!(trace, tracer_span) block.call(Span.new(tracer_span, tags)) end else tracer_span = Datadog::Tracing.trace(span_name, **span_options) - trace = Datadog::Tracing.active_trace - set_internal_tracing_context!(trace, tracer_span) Span.new(tracer_span, tags) end end + def active_test + @local_context.active_test + end + + def deactivate_test(test) + @local_context.deactivate_test!(test) + end + + def active_span + tracer_span = Datadog::Tracing.active_span + Span.new(tracer_span) if tracer_span + end + + private + def set_internal_tracing_context!(trace, span) # Sets trace's origin to ciapp-test trace.origin = Ext::Test::CONTEXT_ORIGIN if trace diff --git a/lib/datadog/ci/span.rb b/lib/datadog/ci/span.rb index 5f96f23a..d6bf8c04 100644 --- a/lib/datadog/ci/span.rb +++ b/lib/datadog/ci/span.rb @@ -46,6 +46,10 @@ def finish tracer_span.finish end + def span_type + tracer_span.type + end + private def set_tags!(tags) diff --git a/lib/datadog/ci/test.rb b/lib/datadog/ci/test.rb new file mode 100644 index 00000000..4dee1471 --- /dev/null +++ b/lib/datadog/ci/test.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require_relative "span" + +module Datadog + module CI + class Test < Span + def finish + super + + CI.deactivate_test(self) + end + end + end +end diff --git a/sig/datadog/ci.rbs b/sig/datadog/ci.rbs index 560eea99..9a1b76b4 100644 --- a/sig/datadog/ci.rbs +++ b/sig/datadog/ci.rbs @@ -1,11 +1,17 @@ module Datadog module CI - def self.trace_test: (String span_name, ?service_name: String?, ?operation_name: String, ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Span span) -> untyped } -> untyped + def self.trace_test: (String span_name, ?service_name: String?, ?operation_name: String, ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Test test) -> untyped } -> untyped - def self.start_test: (String span_name, ?service_name: String?, ?operation_name: String, ?tags: Hash[untyped, untyped]) -> Datadog::CI::Span + def self.start_test: (String span_name, ?service_name: String?, ?operation_name: String, ?tags: Hash[untyped, untyped]) -> Datadog::CI::Test def self.trace: (String span_type, String span_name, ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Span span) -> untyped } -> untyped + def self.active_test: () -> Datadog::CI::Test? + + def self.active_span: (String span_type) -> Datadog::CI::Span? + + def self.deactivate_test: (Datadog::CI::Test test) -> void + def self.components: () -> Datadog::CI::Configuration::Components def self.recorder: () -> Datadog::CI::Recorder diff --git a/sig/datadog/ci/context.rbs b/sig/datadog/ci/context.rbs deleted file mode 100644 index b6d50685..00000000 --- a/sig/datadog/ci/context.rbs +++ /dev/null @@ -1,14 +0,0 @@ -module Datadog - module CI - class Context - @active_test: untyped - - include Core::Utils::Forking - - attr_reader active_test: untyped - - def initialize: (?test: untyped?) -> void - def fork_clone: () -> untyped - end - end -end diff --git a/sig/datadog/ci/context/local.rbs b/sig/datadog/ci/context/local.rbs new file mode 100644 index 00000000..48dead81 --- /dev/null +++ b/sig/datadog/ci/context/local.rbs @@ -0,0 +1,26 @@ +module Datadog + module CI + module Context + class Local + @key: String + + def initialize: () -> void + + def activate_test!: (Datadog::CI::Test test) ?{ () -> untyped } -> void + + def deactivate_test!: (Datadog::CI::Test test) -> void + + def active_test: () -> Datadog::CI::Test? + + UNIQUE_INSTANCE_MUTEX: untyped + UNIQUE_INSTANCE_GENERATOR: untyped + + def self.next_instance_id: () -> untyped + + private + + def active_test=: (Datadog::CI::Test? test) -> untyped + end + end + end +end diff --git a/sig/datadog/ci/context_provider.rbs b/sig/datadog/ci/context_provider.rbs deleted file mode 100644 index 40483f7f..00000000 --- a/sig/datadog/ci/context_provider.rbs +++ /dev/null @@ -1,24 +0,0 @@ -module Datadog - module CI - class ContextProvider - @context: untyped - def initialize: () -> void - def context=: (untyped ctx) -> untyped - def context: (?untyped? key) -> untyped - end - - class FiberLocalContext - @key: untyped - - def initialize: () -> void - def local=: (untyped ctx) -> untyped - def local: (?untyped storage) -> untyped - - UNIQUE_INSTANCE_MUTEX: untyped - - UNIQUE_INSTANCE_GENERATOR: untyped - - def self.next_instance_id: () -> untyped - end - end -end diff --git a/sig/datadog/ci/recorder.rbs b/sig/datadog/ci/recorder.rbs index 75ce584b..68b0a2bd 100644 --- a/sig/datadog/ci/recorder.rbs +++ b/sig/datadog/ci/recorder.rbs @@ -2,13 +2,20 @@ module Datadog module CI class Recorder @environment_tags: Hash[String, String] + @local_context: Datadog::CI::Context::Local attr_reader environment_tags: Hash[String, String] - def trace_test: (String span_name, ?service_name: String?, ?operation_name: String, ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Span span) -> untyped } -> untyped + def trace_test: (String span_name, ?service_name: String?, ?operation_name: String, ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Test span) -> untyped } -> untyped def trace: (String span_type, String span_name, ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Span span) -> untyped } -> untyped + def active_test: () -> Datadog::CI::Test? + + def active_span: () -> Datadog::CI::Span? + + def deactivate_test: (Datadog::CI::Test test) -> void + def create_datadog_span: (String span_name, ?span_options: Hash[untyped, untyped], ?tags: Hash[untyped, untyped]) ?{ (Datadog::CI::Span span) -> untyped } -> untyped def set_internal_tracing_context!: (Datadog::Tracing::TraceOperation trace, Datadog::Tracing::SpanOperation span) -> untyped diff --git a/sig/datadog/ci/span.rbs b/sig/datadog/ci/span.rbs index 10fedc27..a6c42562 100644 --- a/sig/datadog/ci/span.rbs +++ b/sig/datadog/ci/span.rbs @@ -13,11 +13,13 @@ module Datadog def skipped!: (?exception: untyped?, ?reason: String?) -> void - def set_tag: (String key, untyped? value) -> untyped + def set_tag: (String key, untyped? value) -> void - def set_metric: (String key, untyped value) -> untyped + def set_metric: (String key, untyped value) -> void - def finish: () -> untyped + def finish: () -> void + + def span_type: () -> String private diff --git a/sig/datadog/ci/test.rbs b/sig/datadog/ci/test.rbs new file mode 100644 index 00000000..6e247463 --- /dev/null +++ b/sig/datadog/ci/test.rbs @@ -0,0 +1,7 @@ +module Datadog + module CI + class Test < Span + def finish: () -> void + end + end +end diff --git a/spec/datadog/ci/context/local_spec.rb b/spec/datadog/ci/context/local_spec.rb new file mode 100644 index 00000000..88a78d36 --- /dev/null +++ b/spec/datadog/ci/context/local_spec.rb @@ -0,0 +1,134 @@ +RSpec.describe Datadog::CI::Context::Local do + subject { described_class.new } + + let(:tracer_span) { double(Datadog::Tracing::SpanOperation) } + let(:ci_test) { Datadog::CI::Test.new(tracer_span) } + let(:ci_test2) { Datadog::CI::Test.new(tracer_span) } + + def fiber_active_tests + Thread.current.keys.select { |k| k.to_s.start_with?("datadog_ci_active_test_") } + end + + describe "#activate_test!" do + context "when a test is already active" do + it "raises an error" do + subject.activate_test!(Datadog::CI::Test.new(tracer_span)) + + expect { subject.activate_test!(ci_test) }.to raise_error(RuntimeError, /Nested tests are not supported/) + end + end + + context "when no test is active" do + context "when a block is given" do + it "activates the test for the duration of the block" do + subject.activate_test!(ci_test) do + expect(subject.active_test).to be(ci_test) + end + + expect(subject.active_test).to be_nil + end + end + + context "when no block is given" do + it "activates the test" do + subject.activate_test!(ci_test) + expect(subject.active_test).to be(ci_test) + end + end + end + + context "with multiple local contexts" do + let(:local_context_1) { described_class.new } + let(:local_context_2) { described_class.new } + + it "does not share the active test" do + local_context_1.activate_test!(ci_test) + local_context_2.activate_test!(ci_test2) + + expect(local_context_1.active_test).to be(ci_test) + expect(local_context_2.active_test).to be(ci_test2) + end + end + + context "with multiple fibers" do + it "create one fiber-local variable per fiber" do + subject.activate_test!(ci_test) + + Fiber.new do + expect { subject.activate_test!(ci_test2) } + .to change { fiber_active_tests.size }.from(0).to(1) + end.resume + + expect(subject.active_test).to be(ci_test) + end + end + + context "with multiple threads" do + it "create one thread-local variable per thread" do + subject.activate_test!(ci_test) + + Thread.new do + expect { subject.activate_test!(ci_test2) } + .to change { fiber_active_tests.size }.from(0).to(1) + end.join + + expect(subject.active_test).to be(ci_test) + end + end + end + + describe "#deactivate_test!" do + context "when no test is active" do + it "does nothing" do + expect { subject.deactivate_test!(ci_test) }.not_to raise_error + end + end + + context "when a test is active" do + before { subject.activate_test!(ci_test) } + + context "when the test is the active test" do + it "deactivates the test" do + subject.deactivate_test!(ci_test) + expect(subject.active_test).to be_nil + end + end + + context "when the test is not the active test" do + it "raises an error" do + expect { subject.deactivate_test!(Datadog::CI::Test.new(tracer_span)) } + .to raise_error(RuntimeError, /Trying to deactivate test/) + end + end + end + end + + describe "#active_test" do + context "when no test is active" do + it "returns nil" do + expect(subject.active_test).to be_nil + end + end + + context "when a test is active" do + before { subject.activate_test!(ci_test) } + + it "returns the active test" do + expect(subject.active_test).to be(ci_test) + end + end + + context "with multiple local contexts" do + let(:local_context_1) { described_class.new } + let(:local_context_2) { described_class.new } + + it "does not share the active test" do + local_context_1.activate_test!(ci_test) + local_context_2.activate_test!(ci_test2) + + expect(local_context_1.active_test).to be(ci_test) + expect(local_context_2.active_test).to be(ci_test2) + end + end + end +end diff --git a/spec/datadog/ci/recorder_spec.rb b/spec/datadog/ci/recorder_spec.rb index af14fba3..778a8475 100644 --- a/spec/datadog/ci/recorder_spec.rb +++ b/spec/datadog/ci/recorder_spec.rb @@ -2,6 +2,8 @@ let(:trace_op) { instance_double(Datadog::Tracing::TraceOperation) } let(:service) { "service" } let(:operation_name) { "span name" } + let(:test_name) { "test name" } + let(:tags) { {} } subject(:recorder) { described_class.new } @@ -19,9 +21,6 @@ end describe "#trace_test" do - let(:tags) { {} } - let(:test_name) { "test name" } - let(:expected_tags) do tags .merge(Datadog::CI::Ext::Environment.tags(ENV)) @@ -149,7 +148,6 @@ trace end - it_behaves_like "internal tracing context" it { expect(block_spy).to have_received(:call).with(ci_span) } it { is_expected.to be(block_result) } end @@ -183,8 +181,63 @@ trace end - it_behaves_like "internal tracing context" it { is_expected.to be(ci_span) } end end + + describe "#active_test" do + subject(:active_test) { recorder.active_test } + + let(:ci_test) do + recorder.trace_test( + test_name, + service_name: service, + operation_name: operation_name, + tags: tags + ) + end + + before { ci_test } + + it { is_expected.to be(ci_test) } + end + + describe "#deactivate_test" do + subject(:deactivate_test) { recorder.deactivate_test(ci_test) } + + let(:ci_test) do + recorder.trace_test( + test_name, + service_name: service, + operation_name: operation_name, + tags: tags + ) + end + + before { deactivate_test } + + it { expect(recorder.active_test).to be_nil } + end + + describe "#active_span" do + subject(:active_span) { recorder.active_span } + + 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 + 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) + 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) } + + it { is_expected.to be_nil } + end + end end diff --git a/spec/datadog/ci/span_spec.rb b/spec/datadog/ci/span_spec.rb index 340a4c3b..266ba2fa 100644 --- a/spec/datadog/ci/span_spec.rb +++ b/spec/datadog/ci/span_spec.rb @@ -143,4 +143,13 @@ span.finish end end + + describe "#span_type" do + let(:tracer_span) { instance_double(Datadog::Tracing::SpanOperation, type: "test") } + let(:span) { described_class.new(tracer_span) } + + it "returns 'test'" do + expect(span.span_type).to eq("test") + end + end end diff --git a/spec/datadog/ci/test_spec.rb b/spec/datadog/ci/test_spec.rb new file mode 100644 index 00000000..8e5a4d5e --- /dev/null +++ b/spec/datadog/ci/test_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +RSpec.describe Datadog::CI::Test do + let(:tracer_span) { instance_double(Datadog::Tracing::SpanOperation, finish: true) } + + describe "#finish" do + subject(:ci_test) { described_class.new(tracer_span) } + + before { allow(Datadog::CI).to receive(:deactivate_test) } + + it "deactivates the test" do + ci_test.finish + expect(Datadog::CI).to have_received(:deactivate_test).with(ci_test) + end + end +end diff --git a/spec/datadog/ci_spec.rb b/spec/datadog/ci_spec.rb index c298f55e..bee49fbd 100644 --- a/spec/datadog/ci_spec.rb +++ b/spec/datadog/ci_spec.rb @@ -1,4 +1,10 @@ RSpec.describe Datadog::CI do + let(:recorder) { instance_double(Datadog::CI::Recorder) } + + before do + allow(Datadog::CI).to receive(:recorder).and_return(recorder) + end + describe "::trace_test" do subject(:trace_test) { described_class.trace_test(test_name, **options, &block) } @@ -12,19 +18,16 @@ end let(:block) { proc {} } - let(:recorder) { instance_double(Datadog::CI::Recorder) } - let(:ci_test) { instance_double(Datadog::CI::Span) } + let(:ci_test) { instance_double(Datadog::CI::Test) } before do - allow(Datadog::CI).to receive(:recorder).and_return(recorder) - allow(recorder).to receive(:trace_test).with(test_name, **options, &block).and_return(ci_test) end it { is_expected.to be(ci_test) } end - describe "#start_test" do + describe "::start_test" do subject(:start_test) { described_class.start_test(test_name, **options) } let(:test_name) { "test name" } @@ -36,12 +39,9 @@ } end - let(:recorder) { instance_double(Datadog::CI::Recorder) } - let(:ci_test) { instance_double(Datadog::CI::Span) } + let(:ci_test) { instance_double(Datadog::CI::Test) } before do - allow(Datadog::CI).to receive(:recorder).and_return(recorder) - allow(recorder).to receive(:trace_test).with(test_name, **options).and_return(ci_test) end @@ -56,15 +56,46 @@ let(:options) { {tags: {"foo" => "bar"}} } let(:block) { proc {} } - let(:recorder) { instance_double(Datadog::CI::Recorder) } let(:ci_span) { instance_double(Datadog::CI::Span) } before do - allow(Datadog::CI).to receive(:recorder).and_return(recorder) - allow(recorder).to receive(:trace).with(span_type, span_name, **options, &block).and_return(ci_span) end it { is_expected.to be(ci_span) } end + + describe "::active_span" do + subject(:active_span) { described_class.active_span(span_type) } + + let(:span_type) { "span type" } + + context "when span type matches current active span" do + let(:ci_span) { instance_double(Datadog::CI::Span, span_type: span_type) } + + before do + allow(recorder).to receive(:active_span).and_return(ci_span) + end + + it { is_expected.to be(ci_span) } + end + + context "when span type does not match current active span" do + let(:ci_span) { instance_double(Datadog::CI::Span, span_type: "other span type") } + + before do + allow(recorder).to receive(:active_span).and_return(ci_span) + end + + it { is_expected.to be_nil } + end + + context "when no active span" do + before do + allow(recorder).to receive(:active_span).and_return(nil) + end + + it { is_expected.to be_nil } + end + end end diff --git a/spec/support/tracer_helpers.rb b/spec/support/tracer_helpers.rb index 73a0e914..9eabe73a 100644 --- a/spec/support/tracer_helpers.rb +++ b/spec/support/tracer_helpers.rb @@ -40,8 +40,8 @@ def produce_test_trace( end end - Datadog::Tracing.active_span.set_tag("test_owner", "my_team") - Datadog::Tracing.active_span.set_metric("memory_allocations", 16) + Datadog::CI.active_test.set_tag("test_owner", "my_team") + Datadog::CI.active_test.set_metric("memory_allocations", 16) case result when "FAILED" diff --git a/vendor/rbs/ddtrace/0/datadog/core/utils/sequence.rbs b/vendor/rbs/ddtrace/0/datadog/core/utils/sequence.rbs new file mode 100644 index 00000000..588548a0 --- /dev/null +++ b/vendor/rbs/ddtrace/0/datadog/core/utils/sequence.rbs @@ -0,0 +1,11 @@ +module Datadog + module Core + module Utils + class Sequence + def initialize: (?::Integer seed) ?{ () -> untyped } -> untyped + + def next: () -> untyped + end + end + end +end \ No newline at end of file diff --git a/vendor/rbs/ddtrace/0/datadog/tracing.rbs b/vendor/rbs/ddtrace/0/datadog/tracing.rbs index a195f44b..2324f32e 100644 --- a/vendor/rbs/ddtrace/0/datadog/tracing.rbs +++ b/vendor/rbs/ddtrace/0/datadog/tracing.rbs @@ -1,6 +1,7 @@ module Datadog module Tracing def self.active_trace: () -> Datadog::Tracing::TraceOperation + def self.active_span: () -> Datadog::Tracing::SpanOperation def self.trace: (String span_name, Hash[untyped, untyped] options) ?{ (untyped span, untyped trace) -> untyped } -> Datadog::Tracing::SpanOperation end end