Module: Buildkite::TestCollector

Defined in:
lib/buildkite/test_collector.rb,
lib/buildkite/test_collector.rb,
lib/buildkite/test_collector/otel.rb,
lib/buildkite/test_collector/error.rb,
lib/buildkite/test_collector/trace.rb,
lib/buildkite/test_collector/object.rb,
lib/buildkite/test_collector/tracer.rb,
lib/buildkite/test_collector/network.rb,
lib/buildkite/test_collector/session.rb,
lib/buildkite/test_collector/version.rb,
lib/buildkite/test_collector/uploader.rb,
lib/buildkite/test_collector/http_client.rb,
lib/buildkite/test_collector/otel/span_filter.rb,
lib/buildkite/test_collector/otel/child_span_forwarder.rb,
lib/buildkite/test_collector/otel/span_metrics_reporter.rb,
lib/buildkite/test_collector/otel/test_span_metrics_reporter.rb,
lib/buildkite/test_collector/otel/child_span_metrics_reporter.rb

Defined Under Namespace

Modules: CucumberPlugin, MinitestPlugin, OTel, RSpecPlugin Classes: CI, Error, HTTPClient, MinDurationSpanFilter, Network, Object, Session, TimeoutError, Trace, Tracer, UUID, Uploader

Constant Summary collapse

DEFAULT_URL =
"https://analytics-api.buildkite.com/v1/uploads"
DEFAULT_UPLOAD_BATCH_SIZE =
500
VERSION =
"2.16.0"
NAME =
"buildkite-test_collector"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.api_tokenObject

Returns the value of attribute api_token.



34
35
36
# File 'lib/buildkite/test_collector.rb', line 34

def api_token
  @api_token
end

.artifact_pathObject

Returns the value of attribute artifact_path.



39
40
41
# File 'lib/buildkite/test_collector.rb', line 39

def artifact_path
  @artifact_path
end

.batch_sizeObject

Returns the value of attribute batch_size.



45
46
47
# File 'lib/buildkite/test_collector.rb', line 45

def batch_size
  @batch_size
end

.envObject

Returns the value of attribute env.



42
43
44
# File 'lib/buildkite/test_collector.rb', line 42

def env
  @env
end

.location_prefixObject

Returns the value of attribute location_prefix.



40
41
42
# File 'lib/buildkite/test_collector.rb', line 40

def location_prefix
  @location_prefix
end

.otel_enabledObject

Returns the value of attribute otel_enabled.



44
45
46
# File 'lib/buildkite/test_collector.rb', line 44

def otel_enabled
  @otel_enabled
end

.sessionObject

Returns the value of attribute session.



37
38
39
# File 'lib/buildkite/test_collector.rb', line 37

def session
  @session
end

.span_filtersObject

Returns the value of attribute span_filters.



47
48
49
# File 'lib/buildkite/test_collector.rb', line 47

def span_filters
  @span_filters
end

.tagsObject

Returns the value of attribute tags.



43
44
45
# File 'lib/buildkite/test_collector.rb', line 43

def tags
  @tags
end

.test_runnerObject

Returns the value of attribute test_runner.



41
42
43
# File 'lib/buildkite/test_collector.rb', line 41

def test_runner
  @test_runner
end

.trace_min_durationObject

Returns the value of attribute trace_min_duration.



46
47
48
# File 'lib/buildkite/test_collector.rb', line 46

def trace_min_duration
  @trace_min_duration
end

.tracing_enabledObject

Returns the value of attribute tracing_enabled.



38
39
40
# File 'lib/buildkite/test_collector.rb', line 38

def tracing_enabled
  @tracing_enabled
end

.uploaderObject

Returns the value of attribute uploader.



36
37
38
# File 'lib/buildkite/test_collector.rb', line 36

def uploader
  @uploader
end

.urlObject

Returns the value of attribute url.



35
36
37
# File 'lib/buildkite/test_collector.rb', line 35

def url
  @url
end

Class Method Details

.annotate(content) ⇒ Object



156
157
158
159
160
161
162
# File 'lib/buildkite/test_collector.rb', line 156

def self.annotate(content)
  return Buildkite::TestCollector::OTel.annotate(content) if otel_enabled?

  tracer = Buildkite::TestCollector::Uploader.tracer
  tracer&.enter("annotation", **{ content: content })
  tracer&.leave
end

.configure(hook:, token: nil, url: nil, tracing_enabled: true, artifact_path: nil, location_prefix: nil, env: {}, tags: {}, otel_enabled: false, otel_span_filter: nil) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/buildkite/test_collector.rb', line 50

def self.configure(hook:, token: nil, url: nil, tracing_enabled: true, artifact_path: nil, location_prefix: nil, env: {}, tags: {}, otel_enabled: false, otel_span_filter: nil)
  # A filter that raises at runtime fails open, but an object that cannot be
  # called at all is a configuration mistake worth surfacing immediately.
  unless otel_span_filter.nil? || otel_span_filter.respond_to?(:call)
    raise ArgumentError, "otel_span_filter must respond to #call"
  end

  self.api_token = (token || ENV["BUILDKITE_ANALYTICS_TOKEN"])&.strip
  self.url = url || ENV["BUILDKITE_ANALYTICS_ENDPOINT"] || DEFAULT_URL
  self.tracing_enabled = tracing_enabled
  self.artifact_path = artifact_path
  self.location_prefix = location_prefix || ENV["BUILDKITE_ANALYTICS_LOCATION_PREFIX"]
  self.test_runner = hook.to_s
  self.env = env
  self.tags = worker_id_tag.merge(tags)
  if otel_enabled && test_runner != "rspec"
    warn "[buildkite-test_collector] otel_enabled is only supported with the rspec hook, " \
      "not #{test_runner}; #{json_fallback_outcome}"
    otel_enabled = false
  end
  # Without a credential there is nothing to submit; stay quiet like the
  # JSON path (Uploader.upload skips the request without a token) rather
  # than warn a developer running the suite locally.
  if otel_enabled
    otel_token = Buildkite::TestCollector::OTel.api_token || api_token
    otel_enabled = false if otel_token.nil?
  end
  self.otel_enabled = otel_enabled
  self.batch_size = ENV.fetch("BUILDKITE_ANALYTICS_UPLOAD_BATCH_SIZE") { DEFAULT_UPLOAD_BATCH_SIZE }.to_i

  trace_min_ms_string = ENV["BUILDKITE_ANALYTICS_TRACE_MIN_MS"]
  self.trace_min_duration = if trace_min_ms_string && !trace_min_ms_string.empty?
    Float(trace_min_ms_string) / 1000
  end

  self.span_filters = []
  unless self.trace_min_duration.nil?
    self.span_filters << MinDurationSpanFilter.new(self.trace_min_duration)
  end

  # Defer OTel setup until RSpec's before(:suite), after application and support files have loaded.
  @otel_options = if otel_enabled?
    {
      endpoint: Buildkite::TestCollector::OTel.endpoint,
      api_token: otel_token,
      run_env: Buildkite::TestCollector::CI.env,
      span_filter: otel_span_filter,
      # Include the automatic worker tag alongside caller-supplied tags.
      tags: self.tags,
    }
  end
  self.hook_into(hook)
  enable_tracing! if test_runner == "rspec" && !otel_enabled?
end

.enable_tracing!Object



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/buildkite/test_collector.rb', line 176

def self.enable_tracing!
  return unless self.tracing_enabled

  Buildkite::TestCollector::Network.configure
  Buildkite::TestCollector::Object.configure

  return unless defined?(ActiveSupport)

  require "active_support/notifications"
  return if @active_support_tracing_enabled

  ActiveSupport::Notifications.subscribe("sql.active_record") do |name, start, finish, id, payload|
    Buildkite::TestCollector::Uploader.tracer&.backfill(:sql, finish - start, **{ query: payload[:sql] })
  end
  @active_support_tracing_enabled = true
end

.hook_into(hook) ⇒ Object



137
138
139
140
141
142
# File 'lib/buildkite/test_collector.rb', line 137

def self.hook_into(hook)
  file = "test_collector/library_hooks/#{hook}"
  require_relative file
rescue LoadError
  raise ArgumentError.new("#{hook.inspect} is not a supported Buildkite Analytics Test library hook.")
end

.otel_enabled?Boolean

Returns:

  • (Boolean)


133
134
135
# File 'lib/buildkite/test_collector.rb', line 133

def self.otel_enabled?
  !!otel_enabled
end

.start_otelObject



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/buildkite/test_collector.rb', line 105

def self.start_otel
  options = @otel_options
  @otel_options = nil
  return unless options

  Buildkite::TestCollector::OTel.configure!(**options)
  return if Buildkite::TestCollector::OTel.enabled?

  # Nothing has run yet, so the legacy path can still cover the whole
  # suite: the Reporter, per-example tracer, and artifact all read
  # otel_enabled? lazily.
  warn "[buildkite-test_collector] otel_enabled is set, but OpenTelemetry could not be configured " \
    "(see the warning above); #{json_fallback_outcome}"
  self.otel_enabled = false
  enable_tracing!
end

.tag_execution(key, value) ⇒ Object

Set a key=value tag on the current test execution.



165
166
167
168
169
170
171
172
173
174
# File 'lib/buildkite/test_collector.rb', line 165

def self.tag_execution(key, value)
  tags = Thread.current[:_buildkite_tags]
  raise "_buildkite_tags not available" unless tags

  unless key.is_a?(String) && value.is_a?(String)
    raise ArgumentError, "tag key and value expected string"
  end

  tags[key] = value
end