Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CIVIS-7950] Add rspec instrumentation for test suite level visibility #86

Merged
merged 10 commits into from
Dec 13, 2023
4 changes: 4 additions & 0 deletions integration/app/spec/basic_spec.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
require_relative "shared_examples"

RSpec.describe "Let's do some math" do
include_examples "Testing shared examples"

it "add" do
expect(1 + 1).to eq(2)
end
Expand Down
7 changes: 7 additions & 0 deletions integration/app/spec/shared_examples.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
RSpec.shared_examples "Testing shared examples" do
context "shared examples" do
it "adds 1 and 1" do
expect(1 + 1).to eq(2)
end
end
end
3 changes: 0 additions & 3 deletions integration/app/spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@
c.service = "datadog-ci-integration-app"
c.env = "local"

c.tracing.enabled = true
c.tracing.test_mode.enabled = true

c.ci.enabled = true
c.ci.instrument :rspec
end
2 changes: 1 addition & 1 deletion lib/datadog/ci/contrib/rspec/configuration/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions lib/datadog/ci/contrib/rspec/example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ def run(example_group_instance, reporter)

CI.trace_test(
test_name,
metadata[:example_group][:file_path],
metadata[:example_group][:rerun_file_path],
tags: {
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::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]
) do |test_span|
Expand Down
46 changes: 46 additions & 0 deletions lib/datadog/ci/contrib/rspec/example_group.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require_relative "../../ext/test"
require_relative "ext"

module Datadog
module CI
module Contrib
module RSpec
# Instrument RSpec::Core::Example
module ExampleGroup
def self.included(base)
base.singleton_class.prepend(ClassMethods)
end

# Instance methods for configuration
module ClassMethods
def run(reporter = ::RSpec::Core::NullReporter)
return super unless configuration[:enabled]
return super unless top_level?

test_suite = Datadog::CI.start_test_suite(file_path)

result = super

if result
test_suite.passed!
else
test_suite.failed!
end
test_suite.finish

result
end

private

def configuration
Datadog.configuration.ci[:rspec]
end
end
end
end
end
end
end
9 changes: 5 additions & 4 deletions lib/datadog/ci/contrib/rspec/ext.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ module RSpec
# RSpec integration constants
# TODO: mark as `@public_api` when GA, to protect from resource and tag name changes.
module Ext
APP = "rspec"
FRAMEWORK = "rspec"
DEFAULT_SERVICE_NAME = "rspec"

ENV_ENABLED = "DD_TRACE_RSPEC_ENABLED"

# TODO: remove in 1.0
ENV_OPERATION_NAME = "DD_TRACE_RSPEC_OPERATION_NAME"
FRAMEWORK = "rspec"
OPERATION_NAME = "rspec.example"
SERVICE_NAME = "rspec"
TEST_TYPE = "test"
end
end
end
Expand Down
5 changes: 5 additions & 0 deletions lib/datadog/ci/contrib/rspec/patcher.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
# frozen_string_literal: true

require "datadog/tracing/contrib/patcher"

require_relative "example"
require_relative "example_group"
require_relative "runner"

module Datadog
module CI
Expand All @@ -19,6 +22,8 @@ def target_version

def patch
::RSpec::Core::Example.include(Example)
::RSpec::Core::Runner.include(Runner)
::RSpec::Core::ExampleGroup.include(ExampleGroup)
end
end
end
Expand Down
57 changes: 57 additions & 0 deletions lib/datadog/ci/contrib/rspec/runner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

require_relative "../../ext/test"
require_relative "ext"

module Datadog
module CI
module Contrib
module RSpec
# Instrument RSpec::Core::Runner
module Runner
def self.included(base)
base.prepend(InstanceMethods)
end

module InstanceMethods
def run_specs(example_groups)
return super unless configuration[:enabled]

test_session = CI.start_test_session(
tags: {
CI::Ext::Test::TAG_FRAMEWORK => Ext::FRAMEWORK,
CI::Ext::Test::TAG_FRAMEWORK_VERSION => CI::Contrib::RSpec::Integration.version.to_s,
CI::Ext::Test::TAG_TYPE => CI::Ext::Test::TEST_TYPE
},
service: configuration[:service_name]
)

test_module = CI.start_test_module(test_session.name)

result = super

if result != 0
# TODO: repeating this twice feels clunky, we need to remove test_module API before GA
test_module.failed!
test_session.failed!
else
test_module.passed!
test_session.passed!
end
test_module.finish
test_session.finish

result
end

private

def configuration
Datadog.configuration.ci[:rspec]
end
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions lib/datadog/ci/ext/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ module Test
TAG_TYPE = "test.type"
TAG_COMMAND = "test.command"

TEST_TYPE = "test"

# those tags are special and they are used to correlate tests with the test sessions, suites, and modules
TAG_TEST_SESSION_ID = "_test.session_id"
TAG_TEST_MODULE_ID = "_test.module_id"
Expand Down
22 changes: 22 additions & 0 deletions lib/datadog/ci/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ def finish

CI.deactivate_test(self)
end

# Span id of the running test suite this test belongs to.
# @return [String] the span id of the test suite.
def test_suite_id
get_tag(Ext::Test::TAG_TEST_SUITE_ID)
end

def test_suite_name
get_tag(Ext::Test::TAG_SUITE)
end

# Span id of the running test module this test belongs to.
# @return [String] the span id of the test module.
def test_module_id
get_tag(Ext::Test::TAG_TEST_MODULE_ID)
end

# Span id of the running test module this test belongs to.
# @return [String] the span id of the test session.
def test_session_id
get_tag(Ext::Test::TAG_TEST_SESSION_ID)
end
end
end
end
8 changes: 8 additions & 0 deletions lib/datadog/ci/test_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ def finish
CI.deactivate_test_session
end

# Return the test session's name which is equal to test command used
# @return [String] the command for this test session.
def name
get_tag(Ext::Test::TAG_COMMAND)
end

# Return the test session tags that could be inherited by sub-spans
# @return [Hash] the tags to be inherited by sub-spans.
def inheritable_tags
return @inheritable_tags if defined?(@inheritable_tags)

Expand Down
26 changes: 26 additions & 0 deletions lib/datadog/ci/test_visibility/recorder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ def build_test_suite(tracer_span, tags)
def build_test(tracer_span, tags)
test = Test.new(tracer_span)
set_initial_tags(test, tags)
validate_test_suite_level_visibility_correctness(test)
test
end

Expand Down Expand Up @@ -287,6 +288,31 @@ def start_datadog_tracer_span(span_name, span_options, &block)
def null_span
@null_span ||= NullSpan.new
end

def validate_test_suite_level_visibility_correctness(test)
return unless test_suite_level_visibility_enabled

if test.test_suite_id.nil?
Datadog.logger.debug do
"Test [#{test.name}] does not have a test suite associated with it. " \
"Expected test suite [#{test.test_suite_name}] to be running."
end
end

if test.test_module_id.nil?
Datadog.logger.debug do
"Test [#{test.name}] does not have a test module associated with it. " \
"Make sure that there is a test module running within a session."
end
end

if test.test_session_id.nil?
Datadog.logger.debug do
"Test [#{test.name}] does not have a test session associated with it. " \
"Make sure that there is a test session running."
end
end
end
end
end
end
Expand Down
2 changes: 2 additions & 0 deletions sig/datadog/ci.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module Datadog

def self.active_test: () -> Datadog::CI::Test?

def self.active_test_suite: (String test_suite_name) -> Datadog::CI::TestSuite?

def self.active_span: (String span_type) -> Datadog::CI::Span?

def self.deactivate_test: (Datadog::CI::Test test) -> void
Expand Down
21 changes: 21 additions & 0 deletions sig/datadog/ci/contrib/rspec/example_group.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Datadog
module CI
module Contrib
module RSpec
module ExampleGroup
def self.included: (untyped base) -> untyped

module ClassMethods
include ::RSpec::Core::ExampleGroup::ClassMethods

def run: (?untyped reporter) -> untyped

private

def configuration: () -> untyped
end
end
end
end
end
end
10 changes: 2 additions & 8 deletions sig/datadog/ci/contrib/rspec/ext.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,13 @@ module Datadog
module Contrib
module RSpec
module Ext
APP: String

ENV_ENABLED: String

ENV_OPERATION_NAME: String

FRAMEWORK: String
DEFAULT_SERVICE_NAME: String

OPERATION_NAME: String

SERVICE_NAME: String

TEST_TYPE: String
ENV_OPERATION_NAME: String
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions sig/datadog/ci/contrib/rspec/runner.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Datadog
module CI
module Contrib
module RSpec
module Runner
def self.included: (untyped base) -> untyped

module InstanceMethods
include ::RSpec::Core::Runner

def run_specs: (untyped example_groups) -> untyped

private

def configuration: () -> untyped
end
end
end
end
end
end
2 changes: 2 additions & 0 deletions sig/datadog/ci/ext/test.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ module Datadog

TAG_TEST_SUITE_ID: String

TEST_TYPE: String

SPECIAL_TAGS: Array[String]

INHERITABLE_TAGS: Array[String]
Expand Down
4 changes: 4 additions & 0 deletions sig/datadog/ci/test.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ module Datadog
module CI
class Test < Span
def finish: () -> void
def test_suite_id: () -> String?
def test_suite_name: () -> String?
def test_module_id: () -> String?
def test_session_id: () -> String?
end
end
end
2 changes: 2 additions & 0 deletions sig/datadog/ci/test_visibility/recorder.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ module Datadog
def start_datadog_tracer_span: (String span_name, Hash[untyped, untyped] span_options) ?{ (untyped) -> untyped } -> untyped

def set_inherited_globals: (Hash[untyped, untyped] tags) -> void

def validate_test_suite_level_visibility_correctness: (Datadog::CI::Test test) -> void
end
end
end
Expand Down
Loading