diff --git a/.standard_todo.yml b/.standard_todo.yml index d72dcc590..63304fc50 100644 --- a/.standard_todo.yml +++ b/.standard_todo.yml @@ -16,3 +16,5 @@ ignore: - Performance/UnfreezeString - Appraisals: - Style/Alias + - spec/datadog/ci/contrib/minitest/instrumentation_spec.rb: + - Lint/ConstantDefinitionInBlock diff --git a/Steepfile b/Steepfile index e6cdc0c94..dba07a3b9 100644 --- a/Steepfile +++ b/Steepfile @@ -4,6 +4,7 @@ target :lib do check "lib" ignore "lib/datadog/ci/configuration/settings.rb" + ignore "lib/datadog/ci/contrib/minitest/plugin.rb" library "pathname" library "json" @@ -20,4 +21,5 @@ target :lib do library "rspec" library "cucumber" library "msgpack" + library "weakref" end diff --git a/lib/datadog/ci/contrib/minitest/configuration/settings.rb b/lib/datadog/ci/contrib/minitest/configuration/settings.rb index 4a8e7c150..b3caa11ca 100644 --- a/lib/datadog/ci/contrib/minitest/configuration/settings.rb +++ b/lib/datadog/ci/contrib/minitest/configuration/settings.rb @@ -19,7 +19,7 @@ class Settings < Datadog::CI::Contrib::Settings option :service_name do |o| o.type :string - o.default { Datadog.configuration.service_without_fallback || Ext::SERVICE_NAME } + o.default { Datadog.configuration.service_without_fallback || Ext::DEFAULT_SERVICE_NAME } end # @deprecated Will be removed in 1.0 diff --git a/lib/datadog/ci/contrib/minitest/ext.rb b/lib/datadog/ci/contrib/minitest/ext.rb index b45ab2a3a..6fe194ceb 100644 --- a/lib/datadog/ci/contrib/minitest/ext.rb +++ b/lib/datadog/ci/contrib/minitest/ext.rb @@ -7,13 +7,15 @@ module Minitest # Minitest integration constants # TODO: mark as `@public_api` when GA, to protect from resource and tag name changes. module Ext - APP = "minitest" ENV_ENABLED = "DD_TRACE_MINITEST_ENABLED" - ENV_OPERATION_NAME = "DD_TRACE_MINITEST_OPERATION_NAME" + FRAMEWORK = "minitest" + + DEFAULT_SERVICE_NAME = "minitest" + + # TODO: remove in 1.0 + ENV_OPERATION_NAME = "DD_TRACE_MINITEST_OPERATION_NAME" OPERATION_NAME = "minitest.test" - SERVICE_NAME = "minitest" - TEST_TYPE = "test" end end end diff --git a/lib/datadog/ci/contrib/minitest/hooks.rb b/lib/datadog/ci/contrib/minitest/hooks.rb index 570354a85..104e09f09 100644 --- a/lib/datadog/ci/contrib/minitest/hooks.rb +++ b/lib/datadog/ci/contrib/minitest/hooks.rb @@ -24,7 +24,7 @@ def before_setup tags: { CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s, - CI::Ext::Test::TAG_TYPE => Ext::TEST_TYPE + CI::Ext::Test::TAG_TYPE => CI::Ext::Test::TEST_TYPE }, service: configuration[:service_name] ) @@ -51,7 +51,7 @@ def after_teardown private def configuration - ::Datadog.configuration.ci[:minitest] + Datadog.configuration.ci[:minitest] end end end diff --git a/lib/datadog/ci/contrib/minitest/patcher.rb b/lib/datadog/ci/contrib/minitest/patcher.rb index b1b0d73bd..e294d47ae 100644 --- a/lib/datadog/ci/contrib/minitest/patcher.rb +++ b/lib/datadog/ci/contrib/minitest/patcher.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative "hooks" +require_relative "plugin" module Datadog module CI @@ -18,6 +19,9 @@ def target_version def patch ::Minitest::Test.include(Hooks) + ::Minitest.include(Plugin) + + ::Minitest.extensions << "datadog_ci" end end end diff --git a/lib/datadog/ci/contrib/minitest/plugin.rb b/lib/datadog/ci/contrib/minitest/plugin.rb new file mode 100644 index 000000000..4e767d2a4 --- /dev/null +++ b/lib/datadog/ci/contrib/minitest/plugin.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require "weakref" + +require_relative "../../ext/test" +require_relative "ext" + +module Datadog + module CI + module Contrib + module Minitest + module Plugin + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + def plugin_datadog_ci_init(*) + return unless datadog_configuration[:enabled] + + test_session = CI.start_test_session( + tags: { + CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK, + CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::Minitest::Integration.version.to_s, + CI::Ext::Test::TAG_TYPE => CI::Ext::Test::TEST_TYPE + }, + service: datadog_configuration[:service_name] + ) + CI.start_test_module(test_session.name) + + # we create dynamic class here to avoid referencing ::Minitest constant + # in datadog-ci class definitions because Minitest is not always available + datadog_reporter_klass = Class.new(::Minitest::AbstractReporter) do + def initialize(reporter) + # This creates circular reference as reporter holds reference to this reporter. + # To make sure that reporter can be garbage collected, we use WeakRef. + @reporter = WeakRef.new(reporter) + end + + def report + active_test_session = CI.active_test_session + active_test_module = CI.active_test_module + + return unless @reporter.weakref_alive? + return if active_test_session.nil? || active_test_module.nil? + + if @reporter.passed? + active_test_module.passed! + active_test_session.passed! + else + active_test_module.failed! + active_test_session.failed! + end + + active_test_module.finish + active_test_session.finish + end + end + + reporter.reporters << datadog_reporter_klass.new(reporter) + end + + private + + def datadog_configuration + Datadog.configuration.ci[:minitest] + end + end + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/minitest/ext.rbs b/sig/datadog/ci/contrib/minitest/ext.rbs index 9af544e48..8f4847c65 100644 --- a/sig/datadog/ci/contrib/minitest/ext.rbs +++ b/sig/datadog/ci/contrib/minitest/ext.rbs @@ -3,8 +3,6 @@ module Datadog module Contrib module Minitest module Ext - APP: String - ENV_ENABLED: String ENV_OPERATION_NAME: String @@ -13,9 +11,7 @@ module Datadog OPERATION_NAME: String - SERVICE_NAME: String - - TEST_TYPE: String + DEFAULT_SERVICE_NAME: String end end end diff --git a/sig/datadog/ci/contrib/minitest/plugin.rbs b/sig/datadog/ci/contrib/minitest/plugin.rbs new file mode 100644 index 000000000..c6dfd431f --- /dev/null +++ b/sig/datadog/ci/contrib/minitest/plugin.rbs @@ -0,0 +1,26 @@ +module Datadog + module CI + module Contrib + module Minitest + module Plugin + def self.included: (untyped base) -> untyped + + module ClassMethods + attr_reader reporter: WeakRef + @reporter: WeakRef + + def plugin_datadog_ci_init: (*untyped) -> (nil | untyped) + + def initialize: (untyped reporter) -> void + + def report: () -> (nil | untyped) + + private + + def datadog_configuration: () -> untyped + end + end + end + end + end +end diff --git a/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb b/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb index b875c90a9..1ae8c9d12 100644 --- a/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb +++ b/spec/datadog/ci/contrib/minitest/instrumentation_spec.rb @@ -2,7 +2,7 @@ require "minitest" require "minitest/spec" -RSpec.describe "Minitest hooks" do +RSpec.describe "Minitest instrumentation" do include_context "CI mode activated" do let(:integration_name) { :minitest } let(:integration_options) { {service_name: "ltest"} } @@ -34,7 +34,7 @@ def test_foo "spec/datadog/ci/contrib/minitest/instrumentation_spec.rb" ) expect(span.get_tag(Datadog::CI::Ext::Test::TAG_SPAN_KIND)).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST) - expect(span.get_tag(Datadog::CI::Ext::Test::TAG_TYPE)).to eq(Datadog::CI::Contrib::Minitest::Ext::TEST_TYPE) + expect(span.get_tag(Datadog::CI::Ext::Test::TAG_TYPE)).to eq(Datadog::CI::Ext::Test::TEST_TYPE) expect(span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK)).to eq(Datadog::CI::Contrib::Minitest::Ext::FRAMEWORK) expect(span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK_VERSION)).to eq( Datadog::CI::Contrib::Minitest::Integration.version.to_s @@ -352,4 +352,80 @@ def test_foo expect_skip end end + + context "run minitest suite" do + before(:context) do + Minitest::Runnable.reset + + class SomeTest < Minitest::Test + def test_pass + assert true + end + end + end + + before do + Minitest.run([]) + end + + it "creates a test session span" do + expect(test_session_span).not_to be_nil + expect(test_session_span.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST_SESSION) + expect(test_session_span.get_tag(Datadog::CI::Ext::Test::TAG_SPAN_KIND)).to eq( + Datadog::CI::Ext::AppTypes::TYPE_TEST + ) + expect(test_session_span.get_tag(Datadog::CI::Ext::Test::TAG_TYPE)).to eq( + Datadog::CI::Ext::Test::TEST_TYPE + ) + expect(test_session_span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK)).to eq( + Datadog::CI::Contrib::Minitest::Ext::FRAMEWORK + ) + expect(test_session_span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK_VERSION)).to eq( + Datadog::CI::Contrib::Minitest::Integration.version.to_s + ) + expect(test_session_span.get_tag(Datadog::CI::Ext::Test::TAG_STATUS)).to eq( + Datadog::CI::Ext::Test::Status::PASS + ) + end + + it "creates a test module span" do + expect(test_module_span).not_to be_nil + + expect(test_module_span.span_type).to eq(Datadog::CI::Ext::AppTypes::TYPE_TEST_MODULE) + expect(test_module_span.name).to eq(test_command) + + expect(test_module_span.get_tag(Datadog::CI::Ext::Test::TAG_SPAN_KIND)).to eq( + Datadog::CI::Ext::AppTypes::TYPE_TEST + ) + expect(test_module_span.get_tag(Datadog::CI::Ext::Test::TAG_TYPE)).to eq( + Datadog::CI::Ext::Test::TEST_TYPE + ) + expect(test_module_span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK)).to eq( + Datadog::CI::Contrib::Minitest::Ext::FRAMEWORK + ) + expect(test_module_span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK_VERSION)).to eq( + Datadog::CI::Contrib::Minitest::Integration.version.to_s + ) + expect(test_module_span.get_tag(Datadog::CI::Ext::Test::TAG_STATUS)).to eq( + Datadog::CI::Ext::Test::Status::PASS + ) + end + + it "creates test span and connects it to the session and module" do + expect(test_spans.count).to eq(1) + + expect(first_test_span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK)).to eq( + Datadog::CI::Contrib::Minitest::Ext::FRAMEWORK + ) + expect(first_test_span.get_tag(Datadog::CI::Ext::Test::TAG_FRAMEWORK_VERSION)).to eq( + Datadog::CI::Contrib::Minitest::Integration.version.to_s + ) + expect(first_test_span.get_tag(Datadog::CI::Ext::Test::TAG_STATUS)).to eq( + Datadog::CI::Ext::Test::Status::PASS + ) + + expect(first_test_span.get_tag(Datadog::CI::Ext::Test::TAG_TEST_SESSION_ID)).to eq(test_session_span.id.to_s) + expect(first_test_span.get_tag(Datadog::CI::Ext::Test::TAG_TEST_MODULE_ID)).to eq(test_module_span.id.to_s) + end + end end diff --git a/vendor/rbs/weakref/0/weakref.rbs b/vendor/rbs/weakref/0/weakref.rbs new file mode 100644 index 000000000..f53b31a20 --- /dev/null +++ b/vendor/rbs/weakref/0/weakref.rbs @@ -0,0 +1,6 @@ +# as rbs misses delegator support, we make WeakRef a subclass of CompositeReporter +# for our current use case +class WeakRef < Minitest::CompositeReporter + def initialize: (untyped obj) -> untyped + def weakref_alive?: () -> bool +end