Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/run_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,12 @@ jobs:
export RUN_TESTS=1
echo "testing with ruby version: $RUBY_VERSION"
test/test_setup.sh

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage/coverage.xml
flags: ruby-${{ matrix.ruby }}-${{ matrix.os }}
fail_ci_if_error: false
skip_validation: true
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ Style/StringLiterals:
- 'Gemfile'
Naming/PredicateMethod:
Enabled: false
Style/OneClassPerFile:
Enabled: false
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ group :development, :test do
gem 'rubocop-rake', require: false
gem 'code-scanning-rubocop', '~> 0.6.1'
gem 'simplecov', require: false, group: :test
gem 'simplecov-cobertura', require: false, group: :test
gem 'simplecov-console', require: false, group: :test
gem 'webmock' if RUBY_VERSION >= '2.0.0'
gem 'base64' if RUBY_VERSION >= '3.4.0'
Expand Down
103 changes: 103 additions & 0 deletions test/api/api_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

# Copyright (c) 2025 SolarWinds, LLC.
# All rights reserved.

require 'minitest_helper'
require_relative '../../lib/solarwinds_apm/config'
require_relative '../../lib/solarwinds_apm/otel_config'
require './lib/solarwinds_apm/api'

describe 'API::OpenTelemetry#in_span delegation to OpenTelemetry tracer' do
it 'returns nil and warns when block is nil' do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i see the return nil is asserted. how is the "warns when block is nil" tested?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added assertion that logger.warn will be called.

warned = false
SolarWindsAPM.logger.stub(:warn, ->(_msg = nil, &block) { warned = true if block&.call&.include?('please provide block') }) do
result = SolarWindsAPM::API.in_span('test_span')
assert_nil result
end
assert warned, 'Expected a warning to be logged when block is nil'
end

it 'calls in_span with a block and asserts the return value' do
OpenTelemetry::SDK.configure
result = SolarWindsAPM::API.in_span('test_span') do |span|
refute_nil span
42
end
assert_equal 42, result
end

it 'passes attributes, kind and other options to in_span' do
OpenTelemetry::SDK.configure
result = SolarWindsAPM::API.in_span('test_span', attributes: { 'key' => 'value' }, kind: :internal) do |span|
refute_nil span
'done'
end
assert_equal 'done', result
end
end

describe 'API::CustomMetrics deprecated methods return false' do
it 'increment_metric returns false with deprecation' do
result = SolarWindsAPM::API.increment_metric('test_metric', 1, false, {})
assert_equal false, result
end

it 'summary_metric returns false with deprecation' do
result = SolarWindsAPM::API.summary_metric('test_metric', 5.0, 1, false, {})
assert_equal false, result
end
end

describe 'API::Tracer#add_tracer method wrapping with span instrumentation' do
it 'add_tracer wraps an instance method with in_span' do
klass = Class.new do
include SolarWindsAPM::API::Tracer

def greeting
'hello'
end
add_tracer :greeting, 'greeting_span'
end

OpenTelemetry::SDK.configure

instance = klass.new
result = instance.greeting
assert_equal 'hello', result
end

it 'add_tracer uses default span name when nil' do
klass = Class.new do
include SolarWindsAPM::API::Tracer

def work
'done'
end
add_tracer :work
end

OpenTelemetry::SDK.configure

instance = klass.new
result = instance.work
assert_equal 'done', result
end

it 'add_tracer passes options to in_span' do
klass = Class.new do
include SolarWindsAPM::API::Tracer

def compute
100
end
add_tracer :compute, 'compute_span', { attributes: { 'foo' => 'bar' }, kind: :consumer }
end

OpenTelemetry::SDK.configure

instance = klass.new
result = instance.compute
assert_equal 100, result
end
end
143 changes: 143 additions & 0 deletions test/api/current_trace_info_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# frozen_string_literal: true

# Copyright (c) 2025 SolarWinds, LLC.
# All rights reserved.

require 'minitest_helper'
require_relative '../../lib/solarwinds_apm/config'
require_relative '../../lib/solarwinds_apm/otel_config'
require './lib/solarwinds_apm/api'

describe 'API::CurrentTraceInfo#for_log and #hash_for_log with log_traceId configuration' do
describe 'TraceInfo' do
it 'returns empty string for_log when log_traceId is :never' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :never

trace = SolarWindsAPM::API.current_trace_info
assert_equal '', trace.for_log
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns empty hash for hash_for_log when log_traceId is :never' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :never

trace = SolarWindsAPM::API.current_trace_info
assert_equal({}, trace.hash_for_log)
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns trace info for_log when log_traceId is :always' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :always

trace = SolarWindsAPM::API.current_trace_info
result = trace.for_log
assert_match(/trace_id=[0-9a-f]{32}/, result)
assert_match(/span_id=[0-9a-f]{16}/, result)
assert_match(/trace_flags=[0-9a-f]{2}/, result)
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns hash for hash_for_log when log_traceId is :always' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :always

trace = SolarWindsAPM::API.current_trace_info
result = trace.hash_for_log
assert result.key?('trace_id')
assert result.key?('span_id')
assert result.key?('trace_flags')
assert result.key?('resource.service.name')
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns empty for_log when :traced and no active trace' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :traced

trace = SolarWindsAPM::API.current_trace_info
# Without an active trace, trace_id is all zeros, so valid? returns false
assert_equal '', trace.for_log
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns empty for_log when :sampled and not sampled' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :sampled

trace = SolarWindsAPM::API.current_trace_info
# Without an active sampled trace, should return empty
assert_equal '', trace.for_log
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns trace info within an active span for :traced' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :traced

OpenTelemetry::SDK.configure
tracer = OpenTelemetry.tracer_provider.tracer('test')
tracer.in_span('test_span') do
trace = SolarWindsAPM::API.current_trace_info
result = trace.for_log
assert_match(/trace_id=[0-9a-f]{32}/, result)
end
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'returns trace info within a sampled span for :sampled' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :sampled

OpenTelemetry::SDK.configure
tracer = OpenTelemetry.tracer_provider.tracer('test')
tracer.in_span('test_span') do
trace = SolarWindsAPM::API.current_trace_info
result = trace.for_log
# The default sampler records & samples, so this should have trace info
assert_match(/trace_id=[0-9a-f]{32}/, result)
end
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'has boolean do_log attribute' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :always

trace = SolarWindsAPM::API.current_trace_info
assert trace.do_log
ensure
SolarWindsAPM::Config[:log_traceId] = original
end

it 'has trace_id, span_id, trace_flags, tracestring attributes' do
trace = SolarWindsAPM::API.current_trace_info
refute_nil trace.trace_id
refute_nil trace.span_id
refute_nil trace.trace_flags
refute_nil trace.tracestring
end

it 'for_log is memoized' do
original = SolarWindsAPM::Config[:log_traceId]
SolarWindsAPM::Config[:log_traceId] = :always

trace = SolarWindsAPM::API.current_trace_info
result1 = trace.for_log
result2 = trace.for_log
assert_equal result1, result2
ensure
SolarWindsAPM::Config[:log_traceId] = original
end
end
end
6 changes: 3 additions & 3 deletions test/api/custom_instrumentation_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
ENV.delete('OTEL_SERVICE_NAME')
end

it 'test_custom_instrumentation_simple_case' do
it 'creates a span with default name and internal kind when add_tracer wraps a method' do
class MyClass
include SolarWindsAPM::API::Tracer

Expand All @@ -52,7 +52,7 @@ def new_method(param1, param2)
_(finished_spans[0].kind).must_equal(:internal)
end

it 'test_custom_instrumentation_simple_case_with_custom_name_and_options' do
it 'creates a span with custom name, attributes, and kind when options are provided' do
class MyClass
include SolarWindsAPM::API::Tracer

Expand All @@ -77,7 +77,7 @@ def new_method(param1, param2)
_(finished_spans[0].kind).must_equal(:consumer)
end

it 'test_custom_instrumentation_instance_method' do
it 'wraps a class method with add_tracer and applies custom span options' do
class MyClass
def self.new_method(param1, param2)
param1 + param2
Expand Down
2 changes: 1 addition & 1 deletion test/api/opentelemetry_inspan_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
end

describe 'test_in_span_wrapper_from_solarwinds_apm' do
it 'test_in_span' do
it 'creates child spans with correct names and attributes via in_span wrapper' do
skip if finished_spans.empty?

_(finished_spans.size).must_equal(4)
Expand Down
14 changes: 7 additions & 7 deletions test/api/set_transaction_name_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
ENV.delete('SW_APM_ENABLED')
end

it 'set_transaction_name_when_not_sampled' do
it 'stores transaction name in txn_manager when span is not sampled' do
@solarwinds_processor.on_start(@span, OpenTelemetry::Context.current)
OpenTelemetry::Trace.stub(:current_span, @dummy_span) do
result = SolarWindsAPM::API.set_transaction_name('abcdf')
Expand All @@ -32,7 +32,7 @@
_(@solarwinds_processor.txn_manager.get('77cb6ccc522d3106114dd6ecbb70036a-31e175128efc4018')).must_equal 'abcdf'
end

it 'set_multiple_transaction_names_when_sampled' do
it 'overwrites earlier transaction name with the most recent one when sampled' do
@span.context.trace_flags.instance_variable_set(:@flags, 1)
@solarwinds_processor.on_start(@span, OpenTelemetry::Context.current)
OpenTelemetry::Trace.stub(:current_span, @dummy_span) do
Expand All @@ -42,7 +42,7 @@
_(@solarwinds_processor.txn_manager.get('77cb6ccc522d3106114dd6ecbb70036a-31e175128efc4018')).must_equal 'newer-name'
end

it 'set_empty_transaction_name' do
it 'returns false and does not store when transaction name is empty' do
@solarwinds_processor.on_start(@span, OpenTelemetry::Context.current)
OpenTelemetry::Trace.stub(:current_span, @dummy_span) do
result = SolarWindsAPM::API.set_transaction_name('')
Expand All @@ -51,7 +51,7 @@
assert_nil(@solarwinds_processor.txn_manager.get('77cb6ccc522d3106114dd6ecbb70036a-31e175128efc4018'))
end

it 'set_transaction_name_when_current_span_invalid' do
it 'returns false when current span is invalid' do
@solarwinds_processor.on_start(@span, OpenTelemetry::Context.current)
OpenTelemetry::Trace.stub(:current_span, OpenTelemetry::Trace::Span::INVALID) do
result = SolarWindsAPM::API.set_transaction_name('abcdf')
Expand All @@ -60,7 +60,7 @@
assert_nil(@solarwinds_processor.txn_manager.get('77cb6ccc522d3106114dd6ecbb70036a-31e175128efc4018'))
end

it 'set_transaction_name_when_library_noop' do
it 'returns true and stores name when library is in noop mode' do
@solarwinds_processor.on_start(@span, OpenTelemetry::Context.current)
OpenTelemetry::Trace.stub(:current_span, @dummy_span) do
result = SolarWindsAPM::API.set_transaction_name('abcdf')
Expand All @@ -69,13 +69,13 @@
_(@solarwinds_processor.txn_manager.get('77cb6ccc522d3106114dd6ecbb70036a-31e175128efc4018')).must_equal 'abcdf'
end

it 'set_transaction_name_when_library_disabled' do
it 'returns true without error when library is disabled' do
ENV['SW_APM_ENABLED'] = 'false'
result = SolarWindsAPM::API.set_transaction_name('abcdf')
_(result).must_equal true
end

it 'set_transaction_name_truncated_to_256_chars' do
it 'truncates transaction name to 256 characters when name exceeds limit' do
@solarwinds_processor.on_start(@span, OpenTelemetry::Context.current)
OpenTelemetry::Trace.stub(:current_span, @dummy_span) do
long_name = 'a' * 500
Expand Down
6 changes: 3 additions & 3 deletions test/api/tracing_ready_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,21 @@
@memory_exporter.reset
end

it 'default_test_solarwinds_ready' do
it 'returns true with valid default configuration' do
new_config = @config.dup
sampler = SolarWindsAPM::HttpSampler.new(new_config)
replace_sampler(sampler)
_(SolarWindsAPM::API.solarwinds_ready?).must_equal true
end

it 'solarwinds_ready_with_5000_wait_time' do
it 'returns true when given a 5000ms wait time' do
new_config = @config.dup
sampler = SolarWindsAPM::HttpSampler.new(new_config)
replace_sampler(sampler)
_(SolarWindsAPM::API.solarwinds_ready?(5000)).must_equal true
end

it 'solarwinds_ready_with_invalid_collector' do
it 'returns false when collector endpoint is invalid' do
new_config = @config.merge(collector: URI('https://collector.invalid'))
sampler = SolarWindsAPM::HttpSampler.new(new_config)
replace_sampler(sampler)
Expand Down
Loading
Loading