From 9681ab48db6aabafb6dd97d163049251a0e7e4fb Mon Sep 17 00:00:00 2001 From: Andrey Date: Tue, 24 Sep 2024 14:48:35 +0200 Subject: [PATCH] initial simplecov instrumentation to report total coverage percentage --- Steepfile | 1 + lib/datadog/ci.rb | 1 + .../simplecov/configuration/settings.rb | 26 ++++++++++ lib/datadog/ci/contrib/simplecov/ext.rb | 15 ++++++ .../ci/contrib/simplecov/integration.rb | 47 +++++++++++++++++++ lib/datadog/ci/contrib/simplecov/patcher.rb | 28 +++++++++++ .../ci/contrib/simplecov/result_extractor.rb | 33 +++++++++++++ lib/datadog/ci/ext/test.rb | 3 ++ lib/datadog/ci/test_visibility/component.rb | 3 ++ .../ci/test_visibility/total_coverage.rb | 22 +++++++++ .../simplecov/configuration/settings.rbs | 12 +++++ sig/datadog/ci/contrib/simplecov/ext.rbs | 11 +++++ .../ci/contrib/simplecov/integration.rbs | 26 ++++++++++ sig/datadog/ci/contrib/simplecov/patcher.rbs | 15 ++++++ .../ci/contrib/simplecov/result_extractor.rbs | 23 +++++++++ sig/datadog/ci/ext/test.rbs | 2 + .../ci/test_visibility/total_coverage.rbs | 9 ++++ vendor/rbs/simplecov/0/simplecov.rbs | 21 +++++++++ 18 files changed, 298 insertions(+) create mode 100644 lib/datadog/ci/contrib/simplecov/configuration/settings.rb create mode 100644 lib/datadog/ci/contrib/simplecov/ext.rb create mode 100644 lib/datadog/ci/contrib/simplecov/integration.rb create mode 100644 lib/datadog/ci/contrib/simplecov/patcher.rb create mode 100644 lib/datadog/ci/contrib/simplecov/result_extractor.rb create mode 100644 lib/datadog/ci/test_visibility/total_coverage.rb create mode 100644 sig/datadog/ci/contrib/simplecov/configuration/settings.rbs create mode 100644 sig/datadog/ci/contrib/simplecov/ext.rbs create mode 100644 sig/datadog/ci/contrib/simplecov/integration.rbs create mode 100644 sig/datadog/ci/contrib/simplecov/patcher.rbs create mode 100644 sig/datadog/ci/contrib/simplecov/result_extractor.rbs create mode 100644 sig/datadog/ci/test_visibility/total_coverage.rbs create mode 100644 vendor/rbs/simplecov/0/simplecov.rbs diff --git a/Steepfile b/Steepfile index 70b44273..bc484a8a 100644 --- a/Steepfile +++ b/Steepfile @@ -31,4 +31,5 @@ target :lib do library "capybara" library "timecop" library "webmock" + library "simplecov" end diff --git a/lib/datadog/ci.rb b/lib/datadog/ci.rb index 60152014..7d1828fa 100644 --- a/lib/datadog/ci.rb +++ b/lib/datadog/ci.rb @@ -410,6 +410,7 @@ def test_optimisation require_relative "ci/contrib/rspec/integration" require_relative "ci/contrib/minitest/integration" require_relative "ci/contrib/selenium/integration" +require_relative "ci/contrib/simplecov/integration" # Configuration extensions require_relative "ci/configuration/extensions" diff --git a/lib/datadog/ci/contrib/simplecov/configuration/settings.rb b/lib/datadog/ci/contrib/simplecov/configuration/settings.rb new file mode 100644 index 00000000..8ca84ba7 --- /dev/null +++ b/lib/datadog/ci/contrib/simplecov/configuration/settings.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "datadog/core" + +require_relative "../ext" +require_relative "../../settings" + +module Datadog + module CI + module Contrib + module Simplecov + module Configuration + # Custom settings for the Simplecov integration + # @public_api + class Settings < Datadog::CI::Contrib::Settings + option :enabled do |o| + o.type :bool + o.env Ext::ENV_ENABLED + o.default true + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/simplecov/ext.rb b/lib/datadog/ci/contrib/simplecov/ext.rb new file mode 100644 index 00000000..b74f31d1 --- /dev/null +++ b/lib/datadog/ci/contrib/simplecov/ext.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Datadog + module CI + module Contrib + module Simplecov + # Simplecov integration constants + # @public_api + module Ext + ENV_ENABLED = "DD_CIVISIBILITY_SIMPLECOV_INSTRUMENTATION_ENABLED" + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/simplecov/integration.rb b/lib/datadog/ci/contrib/simplecov/integration.rb new file mode 100644 index 00000000..6d7da3af --- /dev/null +++ b/lib/datadog/ci/contrib/simplecov/integration.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require_relative "../integration" +require_relative "configuration/settings" +require_relative "patcher" + +module Datadog + module CI + module Contrib + module Simplecov + # Description of Simplecov integration + class Integration + include Datadog::CI::Contrib::Integration + + MINIMUM_VERSION = Gem::Version.new("0.18.0") + + register_as :simplecov + + def self.version + Gem.loaded_specs["simplecov"]&.version + end + + def self.loaded? + !defined?(::SimpleCov).nil? + end + + def self.compatible? + super && version >= MINIMUM_VERSION + end + + # additional instrumentations for test helpers are auto instrumented on test session start + def auto_instrument? + true + end + + def new_configuration + Configuration::Settings.new + end + + def patcher + Patcher + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/simplecov/patcher.rb b/lib/datadog/ci/contrib/simplecov/patcher.rb new file mode 100644 index 00000000..1e11e01e --- /dev/null +++ b/lib/datadog/ci/contrib/simplecov/patcher.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "datadog/tracing/contrib/patcher" + +require_relative "result_extractor" + +module Datadog + module CI + module Contrib + module Simplecov + # Patcher enables patching of 'SimpleCov' module. + module Patcher + include Datadog::Tracing::Contrib::Patcher + + module_function + + def target_version + Integration.version + end + + def patch + ::SimpleCov.include(ResultExtractor) + end + end + end + end + end +end diff --git a/lib/datadog/ci/contrib/simplecov/result_extractor.rb b/lib/datadog/ci/contrib/simplecov/result_extractor.rb new file mode 100644 index 00000000..fceb2744 --- /dev/null +++ b/lib/datadog/ci/contrib/simplecov/result_extractor.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require "coverage" + +module Datadog + module CI + module Contrib + module Simplecov + module ResultExtractor + def self.included(base) + base.singleton_class.prepend(ClassMethods) + end + + module ClassMethods + def __dd_peek_result + return nil unless datadog_configuration[:enabled] + + result = ::SimpleCov::UselessResultsRemover.call( + ::SimpleCov::ResultAdapter.call(::Coverage.peek_result) + ) + + ::SimpleCov::Result.new(add_not_loaded_files(result)) + end + + def datadog_configuration + Datadog.configuration.ci[:simplecov] + end + end + end + end + end + end +end diff --git a/lib/datadog/ci/ext/test.rb b/lib/datadog/ci/ext/test.rb index 3bebeeac..17a15a6d 100644 --- a/lib/datadog/ci/ext/test.rb +++ b/lib/datadog/ci/ext/test.rb @@ -64,6 +64,9 @@ module Test TAG_EARLY_FLAKE_ENABLED = "test.early_flake.enabled" # true if early flake detection is enabled TAG_EARLY_FLAKE_ABORT_REASON = "test.early_flake.abort_reason" # reason why early flake detection was aborted + # Tags for total code coverage + TAG_CODE_COVERAGE_LINES_PCT = "test.code_coverage.lines_pct" + # internal APM tag to mark a span as a test span TAG_SPAN_KIND = "span.kind" SPAN_KIND_TEST = "test" diff --git a/lib/datadog/ci/test_visibility/component.rb b/lib/datadog/ci/test_visibility/component.rb index d3e3d29b..2c6b9332 100644 --- a/lib/datadog/ci/test_visibility/component.rb +++ b/lib/datadog/ci/test_visibility/component.rb @@ -4,6 +4,7 @@ require_relative "context" require_relative "telemetry" +require_relative "total_coverage" require_relative "../codeowners/parser" require_relative "../contrib/contrib" @@ -188,6 +189,8 @@ def on_test_started(test) def on_test_session_finished(test_session) test_optimisation.write_test_session_tags(test_session) + TotalCoverage.extract_lines_pct(test_session) + Telemetry.event_finished(test_session) end diff --git a/lib/datadog/ci/test_visibility/total_coverage.rb b/lib/datadog/ci/test_visibility/total_coverage.rb new file mode 100644 index 00000000..9a55e108 --- /dev/null +++ b/lib/datadog/ci/test_visibility/total_coverage.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require_relative "../ext/test" + +module Datadog + module CI + module TestVisibility + module TotalCoverage + def self.extract_lines_pct(test_session) + return unless defined?(::SimpleCov) + return unless ::SimpleCov.running + return unless ::SimpleCov.respond_to?(:__dd_peek_result) + + result = ::SimpleCov.__dd_peek_result + return unless result + + test_session.set_tag(Ext::Test::TAG_CODE_COVERAGE_LINES_PCT, result.covered_percent) + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/simplecov/configuration/settings.rbs b/sig/datadog/ci/contrib/simplecov/configuration/settings.rbs new file mode 100644 index 00000000..34b3ff31 --- /dev/null +++ b/sig/datadog/ci/contrib/simplecov/configuration/settings.rbs @@ -0,0 +1,12 @@ +module Datadog + module CI + module Contrib + module Simplecov + module Configuration + class Settings < Datadog::CI::Contrib::Settings + end + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/simplecov/ext.rbs b/sig/datadog/ci/contrib/simplecov/ext.rbs new file mode 100644 index 00000000..7354e3a6 --- /dev/null +++ b/sig/datadog/ci/contrib/simplecov/ext.rbs @@ -0,0 +1,11 @@ +module Datadog + module CI + module Contrib + module Simplecov + module Ext + ENV_ENABLED: "DD_CIVISIBILITY_SIMPLECOV_INSTRUMENTATION_ENABLED" + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/simplecov/integration.rbs b/sig/datadog/ci/contrib/simplecov/integration.rbs new file mode 100644 index 00000000..679df849 --- /dev/null +++ b/sig/datadog/ci/contrib/simplecov/integration.rbs @@ -0,0 +1,26 @@ +module Datadog + module CI + module Contrib + module Simplecov + class Integration + extend Datadog::CI::Contrib::Integration::ClassMethods + include Datadog::CI::Contrib::Integration::InstanceMethods + + MINIMUM_VERSION: Gem::Version + + def self.version: () -> untyped + + def self.loaded?: () -> bool + + def self.compatible?: () -> bool + + def auto_instrument?: () -> bool + + def new_configuration: () -> untyped + + def patcher: () -> untyped + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/simplecov/patcher.rbs b/sig/datadog/ci/contrib/simplecov/patcher.rbs new file mode 100644 index 00000000..30263ca5 --- /dev/null +++ b/sig/datadog/ci/contrib/simplecov/patcher.rbs @@ -0,0 +1,15 @@ +module Datadog + module CI + module Contrib + module Simplecov + module Patcher + include Datadog::Tracing::Contrib::Patcher + + def self?.target_version: () -> untyped + + def self?.patch: () -> untyped + end + end + end + end +end diff --git a/sig/datadog/ci/contrib/simplecov/result_extractor.rbs b/sig/datadog/ci/contrib/simplecov/result_extractor.rbs new file mode 100644 index 00000000..b2a06fad --- /dev/null +++ b/sig/datadog/ci/contrib/simplecov/result_extractor.rbs @@ -0,0 +1,23 @@ +module Coverage + def self.peek_result: () -> untyped +end + +module Datadog + module CI + module Contrib + module Simplecov + module ResultExtractor + def self.included: (untyped base) -> untyped + + module ClassMethods + include ::SimpleCov + + def __dd_peek_result: () -> ::SimpleCov::Result? + + def datadog_configuration: () -> untyped + end + end + end + end + end +end diff --git a/sig/datadog/ci/ext/test.rbs b/sig/datadog/ci/ext/test.rbs index ae3d5f49..b466d82d 100644 --- a/sig/datadog/ci/ext/test.rbs +++ b/sig/datadog/ci/ext/test.rbs @@ -100,6 +100,8 @@ module Datadog METRIC_CPU_COUNT: "_dd.host.vcpu_count" + TAG_CODE_COVERAGE_LINES_PCT: "test.code_coverage.lines_pct" + module Status PASS: "pass" diff --git a/sig/datadog/ci/test_visibility/total_coverage.rbs b/sig/datadog/ci/test_visibility/total_coverage.rbs new file mode 100644 index 00000000..e4b70657 --- /dev/null +++ b/sig/datadog/ci/test_visibility/total_coverage.rbs @@ -0,0 +1,9 @@ +module Datadog + module CI + module TestVisibility + module TotalCoverage + def self.extract_lines_pct: (Datadog::CI::TestSession test_session) -> void + end + end + end +end diff --git a/vendor/rbs/simplecov/0/simplecov.rbs b/vendor/rbs/simplecov/0/simplecov.rbs new file mode 100644 index 00000000..9faf906c --- /dev/null +++ b/vendor/rbs/simplecov/0/simplecov.rbs @@ -0,0 +1,21 @@ +module SimpleCov + def self.running: () -> bool + + def add_not_loaded_files: (untyped result) -> untyped + + def self.__dd_peek_result: () -> SimpleCov::Result? +end + +class SimpleCov::Result + def initialize: (untyped result) -> void + + def covered_percent: () -> Float +end + +class SimpleCov::UselessResultsRemover + def self.call: (untyped result) -> untyped +end + +class SimpleCov::ResultAdapter + def self.call: (untyped result) -> untyped +end