rspec-trace-formatter 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 523ae0ee65d59fbcc8081aefe0da23355846add224b8465d210eea536a93d9a5
4
+ data.tar.gz: a94b8c49db8bf4a1cfd0d00ebdab0136120be9d9d7338294a3b9f173dc612503
5
+ SHA512:
6
+ metadata.gz: 73903d8f6cc20a9301eb92105ddd0ec80b9152c389ed6c855ae46118a8793a91a356c24200d6f71295e2bec289dc6a0223d158a750b1d8cd6da6fef43f45a9b1
7
+ data.tar.gz: 6afed85a1363970c45fa72d0c37412998d59e6d29334f70c18fc98ce7316376fe8a41f06dee84e28d5c6a894dd9a27ce18f45ee5002b842bfb3ca3617ade6801
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v2
13
+ - uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.7
16
+ - name: StandardRB Lint
17
+ run: |
18
+ gem install standardrb
19
+ standardrb --no-fix --format github
20
+ tests:
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ fail-fast: false
24
+ matrix:
25
+ ruby:
26
+ - "2.5"
27
+ - "2.6"
28
+ - "2.7"
29
+ - "3.0"
30
+ - "jruby-9.2"
31
+ - "jruby-9.3"
32
+ - "truffleruby-20"
33
+ - "truffleruby-21"
34
+ - "truffleruby+graalvm-21"
35
+ steps:
36
+ - uses: actions/checkout@v2
37
+ - uses: ruby/setup-ruby@v1
38
+ with:
39
+ ruby-version: ${{ matrix.ruby }}
40
+ bundler-cache: true
41
+ - name: Run tests
42
+ run: |
43
+ bundle exec rspec --format RSpec::Github::Formatter
data/.gitignore ADDED
@@ -0,0 +1,58 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /spec/examples.txt
9
+ /test/tmp/
10
+ /test/version_tmp/
11
+ /tmp/
12
+
13
+ # Used by dotenv library to load environment variables.
14
+ # .env
15
+
16
+ # Ignore Byebug command history file.
17
+ .byebug_history
18
+
19
+ ## Specific to RubyMotion:
20
+ .dat*
21
+ .repl_history
22
+ build/
23
+ *.bridgesupport
24
+ build-iPhoneOS/
25
+ build-iPhoneSimulator/
26
+
27
+ ## Specific to RubyMotion (use of CocoaPods):
28
+ #
29
+ # We recommend against adding the Pods directory to your .gitignore. However
30
+ # you should judge for yourself, the pros and cons are mentioned at:
31
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
32
+ #
33
+ # vendor/Pods/
34
+
35
+ ## Documentation cache and generated files:
36
+ /.yardoc/
37
+ /_yardoc/
38
+ /doc/
39
+ /rdoc/
40
+
41
+ ## Environment normalization:
42
+ /.bundle/
43
+ /vendor/bundle
44
+ /lib/bundler/man/
45
+
46
+ # for a library or gem, you might want to ignore these files since the code is
47
+ # intended to run in multiple environments; otherwise, check them in:
48
+ Gemfile.lock
49
+ .ruby-version
50
+ .ruby-gemset
51
+
52
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
53
+ .rvmrc
54
+
55
+ # Used by RuboCop. Remote config files pulled in from inherit_from directive.
56
+ # .rubocop-https?--*
57
+
58
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :development do
8
+ platforms :ruby do
9
+ gem "pry-byebug", "~> 3.9.0"
10
+ end
11
+
12
+ platforms :jruby do
13
+ gem "pry-debugger-jruby", "~> 2.1.0"
14
+ end
15
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 Zach Thomae
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # rspec-trace-formatter
2
+
3
+ An [RSpec](https://rspec.info) formatter for constructing [trace data](https://opentelemetry.io/docs/concepts/data-sources/#traces) from your specs.
4
+ This library is inspired by [go-test-trace](https://github.com/rakyll/go-test-trace).
5
+
6
+ ## Why Would I Use This?
7
+
8
+ Collecting data from your RSpec tests may be useful to you for a number of reasons:
9
+
10
+ 1. You'd like to see statistical trends in test runtimes, or in your CI/CD pipeline as a whole
11
+ 2. You'd like a dataset containing pass/fail statuses for all tests to help hunt down flakes
12
+ 4. Other things that I can't think of
13
+
14
+ Traces aren't the only choice for collecting this data, but they are a reasonable one.
15
+ With concepts like test files and example groups, test execution naturally maps onto a trace tree.
16
+ The flexibility of tools like [OpenTelemetry](https://opentelemetry.io) when it comes to including arbitrary key-value attribute pairings is useful when instrumenting a library like RSpec because we can preserve as much context about the tests as we like.
17
+ And this test data isn't likely to be valuable long-term, so the standard retention periods for traces are likely to be acceptable.
18
+
19
+ ## What's In The Box?
20
+
21
+ There are three main parts to this library.
22
+
23
+ ### `RSpec::Trace::Formatter`
24
+
25
+ `RSpec::Trace::Formatter` is an RSpec formatter that emits events containing data that's relevant for constructing traces.
26
+ This formatter doesn't create traces -- it only outputs JSON events.
27
+
28
+ This formatter emits events for the most significant lifecycle events in an RSpec suite: the start of the suite, the start/end of each example and example group, and the end of the suite.
29
+ Because all events are timestamped, you can expect accurate timing data.
30
+ It also collects data about the names of the examples and example groups that are run, as well as useful facts like file locations, pass/fail status, &etc.
31
+
32
+ The event format is designed to be redundant when providing facts about examples and example groups, so as to be less prescriptive about how you consume them.
33
+ This may not be the best decision, but it seemed the right way.
34
+
35
+ ### `rspec-trace-consumer`
36
+
37
+ `rspec-trace-consumer` is a script that reads events created by `RSpec::Trace::Formatter` from standard input and emits trace data to an [OpenTelemetry collector](https://opentelemetry.io/docs/collector/).
38
+ The OpenTelemetry SDK can be configured using the standard [`OTEL_*` environment variables](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/sdk-environment-variables.md).
39
+
40
+ If not set by the `OTEL_SERVICE_NAME` environment variable, the service name will be set to `rspec`.
41
+ The name of the root span defaults to "rspec", but you can change that as well with the `RSPEC_TRACE_FORMATTER_ROOT_SPAN_NAME` environment variable.
42
+
43
+ This script uses the [`AlwaysOn` sampler](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#alwayson) to ensure that no data is ever discarded.
44
+
45
+ ### `RSpec::Trace::OpenTelemetryFormatter`
46
+
47
+ `RSpec::Trace::OpenTelemetryFormatter` is a separate RSpec formatter that combines the two pieces above to collect trace events from RSpec tests _and_ send them to an OpenTelemetry collector.
48
+
49
+ Because this uses the (very nice!) [subprocess library](https://github.com/stripe/subprocess), it only works on Ruby platforms where `fork` is supported.
50
+ If you're running in an environment where this isn't supported (e.g. JRuby) you won't be able to use this.
51
+ However, the rest of this library is _expected_ to work for you, and [specifying an `--out` target for `RSpec::Trace::Formatter`](https://relishapp.com/rspec/rspec-core/v/3-10/docs/command-line/format-option) may make this easier.
52
+
53
+ ## How Do I Use It?
54
+
55
+ This library should be used like [any other RSpec formatter](https://relishapp.com/rspec/rspec-core/v/3-10/docs/command-line/format-option), with the assistance of any environment variables that you need to control the OpenTelemetry data.
56
+
57
+ Example of using the `RSpec::Trace::OpenTelemetryFormatter` with representative environment variables set:
58
+
59
+ ```bash
60
+ OTEL_TRACES_EXPORTER=console bundle exec rspec --format RSpec::Trace::OpenTelemetryFormatter
61
+ ```
62
+
63
+ Example of running the `RSpec::Trace::Formatter` by itself and sending the output to `rspec-trace-consumer` separately (in a way that you can surely improve upon):
64
+
65
+ ```bash
66
+ OTEL_TRACES_EXPORTER=console bundle exec rspec --format RSpec::Trace::Formatter --out /tmp/trace-events.jsonl
67
+
68
+ rspec-trace-consumer < /tmp/trace-events.jsonl
69
+ ```
70
+
71
+ ## How Do I Contribute?
72
+
73
+ Very carefully, I hope.
74
+
75
+ One notable fact is that we use [snapshot testing](https://github.com/levinmr/rspec-snapshot) for the class underpinning `rspec-trace-consumer`.
76
+ To keep this reliable, I've defined a custom OpenTelemetry span exporter that includes meaningful-enough data to test with and no execution-specific fields.
77
+
78
+ ## What's Coming Next?
79
+
80
+ This does not yet support providing a [parent trace ID with an environment variable](https://github.com/open-telemetry/opentelemetry-specification/issues/740).
81
+ I haven't done it yet because it's not very helpful to me given my primary use case, but I'll add that soon enough.
82
+
83
+ The fixtures for the snapshot tests for the `Consumer` class are currently system-specific insofar as the exception stack traces are built from real exceptions that include paths from the Ruby installation that generated them.
84
+ Right now they come from my setup, so if anyone else regenerates the fixtures these paths will change.
85
+ I believe that the ideal way to fix this is to provide a containerized development environment for which these paths can be fixed for everyone.
86
+
87
+ ## License
88
+
89
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:test)
5
+ task default: :test
6
+
7
+ desc "Run the tests and update the snapshots"
8
+ task :update_snapshots do
9
+ system("UPDATE_SNAPSHOTS=true rake test")
10
+ end
11
+
12
+ desc "Regenerates example fixtures for tests"
13
+ task :regenerate_examples do
14
+ system("rspec --format RSpec::Trace::Formatter --out spec/fixtures/example_trace_events.jsonl examples")
15
+ end
@@ -0,0 +1,35 @@
1
+ RSpec.describe "examples" do
2
+ describe String do
3
+ it "allows concatenation" do
4
+ expect("foo" + "bar").to eq("foobar")
5
+ end
6
+
7
+ it "fails with bad concatenation" do
8
+ expect("foo" + "bar").to eq("foo_bar")
9
+ end
10
+ end
11
+
12
+ describe Kernel do
13
+ it "sleeps" do
14
+ sleep 3
15
+ end
16
+ end
17
+
18
+ it "has an undefined example"
19
+
20
+ xit "has an example skipped with xit"
21
+
22
+ it "has an explicitly skipped example" do
23
+ skip("Not running this test")
24
+ end
25
+
26
+ it "has a pending example that fails" do
27
+ pending("This feature is not yet implemented")
28
+ expect(GreatClass.start).to eq([])
29
+ end
30
+
31
+ it "has a pending example that passes" do
32
+ pending("This feature is not yet implemented")
33
+ expect(1 + 2).to eq(3)
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift("#{__dir__}/../lib")
4
+
5
+ require "rspec/trace"
6
+
7
+ RSpec::Trace::Consumer.new($stdin).run
data/lefthook.yml ADDED
@@ -0,0 +1,4 @@
1
+ pre-commit:
2
+ commands:
3
+ standardrb:
4
+ run: bundle exec standardrb --force-exclusion --fix {staged_files} && git add {staged_files}
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "opentelemetry/sdk"
5
+ require "opentelemetry/exporter/otlp"
6
+
7
+ module RSpec
8
+ module Trace
9
+ class Consumer
10
+ def initialize(input)
11
+ @input = input
12
+ OpenTelemetry::SDK.configure do |c|
13
+ c.service_name = ENV.fetch("OTEL_SERVICE_NAME", "rspec")
14
+ end
15
+ @tracer_provider = OpenTelemetry.tracer_provider
16
+ @tracer_provider.sampler = OpenTelemetry::SDK::Trace::Samplers::ALWAYS_ON
17
+ @tracer = @tracer_provider.tracer("rspec-trace-formatter", RSpec::Trace::VERSION)
18
+ @spans = []
19
+ # TODO: Not this
20
+ @current_span_key = OpenTelemetry::Trace.const_get(:CURRENT_SPAN_KEY)
21
+ @contexts = [OpenTelemetry::Context.empty]
22
+ @tokens = []
23
+ end
24
+
25
+ def run
26
+ @input.each_line do |line|
27
+ next if line.strip.empty?
28
+
29
+ begin
30
+ event = parse_event(line)
31
+ rescue
32
+ warn "invalid line: #{line}"
33
+ next
34
+ end
35
+
36
+ case event[:event].to_sym
37
+ when :initiated
38
+ root_span_name = ENV.fetch("RSPEC_TRACE_FORMATTER_ROOT_SPAN_NAME", "rspec")
39
+ create_span(name: root_span_name, timestamp: event[:timestamp])
40
+ create_span(name: "examples loading", timestamp: event[:timestamp]) do |span|
41
+ span.add_attributes("rspec.type" => "loading")
42
+ end
43
+ when :start
44
+ complete_span(timestamp: event[:timestamp])
45
+ create_span(name: "examples running", timestamp: event[:timestamp]) do |span|
46
+ add_attributes_to_span(
47
+ span: span,
48
+ attributes: {count: event[:count], type: "suite"},
49
+ attribute_prefix: "rspec"
50
+ )
51
+ end
52
+ when :example_group_started
53
+ create_span(name: event.dig(:group, :description), timestamp: event[:timestamp]) do |span|
54
+ add_attributes_to_span(
55
+ span: span,
56
+ attributes: event[:group].merge(type: "example_group"),
57
+ attribute_prefix: "rspec",
58
+ exclude_attributes: [:description]
59
+ )
60
+ end
61
+ when :example_group_finished
62
+ complete_span(timestamp: event[:timestamp])
63
+ when :example_started
64
+ create_span(name: event.dig(:example, :description), timestamp: event[:timestamp]) do |span|
65
+ add_attributes_to_span(
66
+ span: span,
67
+ attributes: event[:example].merge(type: "example"),
68
+ attribute_prefix: "rspec",
69
+ exclude_attributes: [:description]
70
+ )
71
+ end
72
+ when :example_passed
73
+ complete_span(timestamp: event[:timestamp]) do |span|
74
+ add_attributes_to_span(
75
+ span: span,
76
+ attributes: event.dig(:example, :result),
77
+ attribute_prefix: "rspec.result"
78
+ )
79
+ end
80
+ when :example_pending
81
+ complete_span(timestamp: event[:timestamp]) do |span|
82
+ add_attributes_to_span(
83
+ span: span,
84
+ attributes: event.dig(:example, :result),
85
+ attribute_prefix: "rspec.result"
86
+ )
87
+ end
88
+ when :example_failed
89
+ complete_span(timestamp: event[:timestamp]) do |span|
90
+ add_attributes_to_span(
91
+ span: span,
92
+ attributes: event.dig(:example, :result),
93
+ attribute_prefix: "rspec.result"
94
+ )
95
+ event_attributes = {
96
+ "exception.type" => event.dig(:exception, :type),
97
+ "exception.message" => event.dig(:exception, :message),
98
+ "exception.stacktrace" => event.dig(:exception, :backtrace)
99
+ }
100
+ span.add_event("exception", attributes: event_attributes, timestamp: event[:timestamp])
101
+ span.status = OpenTelemetry::Trace::Status.error
102
+ end
103
+ when :stop
104
+ complete_span(timestamp: event[:timestamp]) until @spans.empty?
105
+ @tracer_provider.force_flush
106
+ exit
107
+ end
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def parse_event(line)
114
+ event = JSON.parse(line, symbolize_names: true)
115
+ event[:timestamp] = Time.parse(event[:timestamp])
116
+ event
117
+ end
118
+
119
+ def create_span(name:, timestamp:)
120
+ @tracer.start_span(name, start_timestamp: timestamp, with_parent: @contexts.last).tap do |span|
121
+ yield span if block_given?
122
+ @spans.push(span)
123
+ @contexts.push(@contexts.last.set_value(@current_span_key, span))
124
+ @tokens.push(OpenTelemetry::Context.attach(@contexts.last))
125
+ end
126
+ end
127
+
128
+ def complete_span(timestamp:)
129
+ @spans.pop.tap do |span|
130
+ yield span if block_given?
131
+ span.finish(end_timestamp: timestamp)
132
+ @contexts.pop
133
+ OpenTelemetry::Context.detach(@tokens.pop)
134
+ end
135
+ end
136
+
137
+ def add_attributes_to_span(span:, attributes:, attribute_prefix: nil, exclude_attributes: [])
138
+ attributes_for_span = attributes.map do |key, value|
139
+ next if value.nil? || exclude_attributes.include?(key)
140
+
141
+ full_key = attribute_prefix ? "#{attribute_prefix}.#{key}" : key.to_s
142
+ [full_key, value]
143
+ end.compact.to_h
144
+
145
+ span.add_attributes(attributes_for_span)
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/time"
4
+ require "active_support/json"
5
+ require "rspec/core"
6
+ require "rspec/core/formatters/base_text_formatter"
7
+
8
+ require_relative "version"
9
+
10
+ module RSpec
11
+ module Trace
12
+ class Formatter < RSpec::Core::Formatters::BaseFormatter
13
+ RSpec::Core::Formatters.register(
14
+ self,
15
+ :start,
16
+ :example_group_started, :example_group_finished,
17
+ :example_started, :example_passed, :example_pending, :example_failed,
18
+ :stop
19
+ )
20
+
21
+ def start(notification)
22
+ start_time = current_time
23
+ output.puts(JSON.dump({
24
+ timestamp: format_time(start_time - notification.load_time.seconds),
25
+ event: :initiated
26
+ }))
27
+ output.puts(JSON.dump({
28
+ timestamp: format_time(start_time),
29
+ event: :start,
30
+ count: notification.count
31
+ }))
32
+ end
33
+
34
+ def example_group_started(notification)
35
+ output.puts(JSON.dump({
36
+ timestamp: current_timestamp,
37
+ event: :example_group_started,
38
+ group: example_group_attributes(notification.group)
39
+ }))
40
+ end
41
+
42
+ def example_group_finished(notification)
43
+ output.puts(JSON.dump({
44
+ timestamp: current_timestamp,
45
+ event: :example_group_finished,
46
+ group: example_group_attributes(notification.group)
47
+ }))
48
+ end
49
+
50
+ def example_started(notification)
51
+ output.puts(JSON.dump({
52
+ timestamp: current_timestamp,
53
+ event: :example_started,
54
+ example: example_attributes(notification.example)
55
+ }))
56
+ end
57
+
58
+ def example_passed(notification)
59
+ output.puts(JSON.dump({
60
+ timestamp: current_timestamp,
61
+ event: :example_passed,
62
+ example: completed_example_attributes(notification.example)
63
+ }))
64
+ end
65
+
66
+ def example_pending(notification)
67
+ output.puts(JSON.dump({
68
+ timestamp: current_timestamp,
69
+ event: :example_pending,
70
+ example: completed_example_attributes(notification.example)
71
+ }))
72
+ end
73
+
74
+ def example_failed(notification)
75
+ output.puts(JSON.dump({
76
+ timestamp: current_timestamp,
77
+ event: :example_failed,
78
+ example: completed_example_attributes(notification.example),
79
+ exception: {
80
+ message: notification.example.exception.message,
81
+ type: notification.example.exception.class.name,
82
+ backtrace: notification.example.exception.full_message(highlight: false, order: :top).encode("UTF-8", invalid: :replace, undef: :replace, replace: "�")
83
+ }
84
+ }))
85
+ end
86
+
87
+ def stop(_notification)
88
+ output.puts(JSON.dump({timestamp: current_timestamp, event: :stop}))
89
+ end
90
+
91
+ private
92
+
93
+ def example_group_attributes(example_group)
94
+ {
95
+ description: example_group.description,
96
+ described_class: example_group.described_class,
97
+ file_path: example_group.file_path,
98
+ location: example_group.location
99
+ }
100
+ end
101
+
102
+ def example_attributes(example)
103
+ {
104
+ description: example.description,
105
+ full_description: example.full_description,
106
+ file_path: example.file_path,
107
+ location: example.location
108
+ }
109
+ end
110
+
111
+ def example_execution_result_attributes(execution_result)
112
+ {
113
+ status: execution_result.status,
114
+ pending_message: execution_result.pending_message,
115
+ pending_fixed: execution_result.pending_fixed
116
+ }
117
+ end
118
+
119
+ def completed_example_attributes(example)
120
+ example_attributes(example).merge({
121
+ result: example_execution_result_attributes(example.execution_result)
122
+ })
123
+ end
124
+
125
+ def format_time(time)
126
+ time.xmlschema(3)
127
+ end
128
+
129
+ def current_time
130
+ if defined?(Timecop)
131
+ Time.now_without_mock_time
132
+ else
133
+ Time.now
134
+ end
135
+ end
136
+
137
+ def current_timestamp
138
+ format_time(current_time)
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "subprocess"
4
+ require_relative "formatter"
5
+
6
+ module RSpec
7
+ module Trace
8
+ class OpenTelemetryFormatter < Formatter
9
+ RSpec::Core::Formatters.register(
10
+ self,
11
+ :start,
12
+ :example_group_started, :example_group_finished,
13
+ :example_started, :example_passed, :example_pending, :example_failed,
14
+ :stop
15
+ )
16
+
17
+ def initialize(output)
18
+ @process = Subprocess::Process.new(["rspec-trace-consumer"], {stdin: Subprocess::PIPE})
19
+ super(@process.stdin)
20
+ end
21
+
22
+ def stop(notification)
23
+ super(notification)
24
+
25
+ @process.wait
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module Trace
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/trace/version"
4
+ require "rspec/trace/formatter"
5
+ require "rspec/trace/open_telemetry_formatter"
6
+ require "rspec/trace/consumer"
7
+
8
+ module RSpec
9
+ module Trace
10
+ end
11
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/rspec/trace/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rspec-trace-formatter"
7
+ spec.version = RSpec::Trace::VERSION
8
+ spec.authors = ["Zach Thomae"]
9
+ spec.email = ["zach@thomae.co"]
10
+
11
+ spec.summary = "Formatter for RSpec to represent test runs as trace events"
12
+ spec.description = "Create traces from RSpec tests using OpenTelemetry or your own tracing library"
13
+ spec.homepage = "https://github.com/zthomae/rspec-trace-formatter"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.5.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = "https://github.com/zthomae/rspec-trace-formatter"
20
+
21
+ spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+
27
+ spec.add_runtime_dependency "opentelemetry-api", "~> 1.0"
28
+ spec.add_runtime_dependency "opentelemetry-exporter-otlp", "~> 0.20"
29
+ spec.add_runtime_dependency "rspec-core", "~> 3.0"
30
+ spec.add_runtime_dependency "subprocess", "~> 1.0"
31
+
32
+ spec.add_development_dependency "activesupport", "~> 6.0"
33
+ spec.add_development_dependency "bundler", "~> 2.1"
34
+ spec.add_development_dependency "lefthook", "~> 0.7.7"
35
+ spec.add_development_dependency "pry", "~> 0.13"
36
+ spec.add_development_dependency "rake", "~> 13.0.6"
37
+ spec.add_development_dependency "rspec", "~> 3.0"
38
+ spec.add_development_dependency "rspec-github", "~> 2.3"
39
+ spec.add_development_dependency "rspec-snapshot", "~> 2.0"
40
+ spec.add_development_dependency "standard", "~> 1.3.0"
41
+ spec.add_development_dependency "timecop", "~> 0.9"
42
+ end
metadata ADDED
@@ -0,0 +1,260 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rspec-trace-formatter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Zach Thomae
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-12-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: opentelemetry-api
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: opentelemetry-exporter-otlp
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.20'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.20'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-core
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: subprocess
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: lefthook
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.7.7
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.7.7
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.13'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.13'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 13.0.6
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 13.0.6
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec-github
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '2.3'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '2.3'
167
+ - !ruby/object:Gem::Dependency
168
+ name: rspec-snapshot
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: standard
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: 1.3.0
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: 1.3.0
195
+ - !ruby/object:Gem::Dependency
196
+ name: timecop
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - "~>"
200
+ - !ruby/object:Gem::Version
201
+ version: '0.9'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - "~>"
207
+ - !ruby/object:Gem::Version
208
+ version: '0.9'
209
+ description: Create traces from RSpec tests using OpenTelemetry or your own tracing
210
+ library
211
+ email:
212
+ - zach@thomae.co
213
+ executables:
214
+ - rspec-trace-consumer
215
+ extensions: []
216
+ extra_rdoc_files: []
217
+ files:
218
+ - ".github/workflows/ci.yml"
219
+ - ".gitignore"
220
+ - ".rspec"
221
+ - Gemfile
222
+ - LICENSE
223
+ - README.md
224
+ - Rakefile
225
+ - examples/example_spec.rb
226
+ - exe/rspec-trace-consumer
227
+ - lefthook.yml
228
+ - lib/rspec/trace.rb
229
+ - lib/rspec/trace/consumer.rb
230
+ - lib/rspec/trace/formatter.rb
231
+ - lib/rspec/trace/open_telemetry_formatter.rb
232
+ - lib/rspec/trace/version.rb
233
+ - rspec-trace-formatter.gemspec
234
+ homepage: https://github.com/zthomae/rspec-trace-formatter
235
+ licenses:
236
+ - MIT
237
+ metadata:
238
+ allowed_push_host: https://rubygems.org
239
+ homepage_uri: https://github.com/zthomae/rspec-trace-formatter
240
+ source_code_uri: https://github.com/zthomae/rspec-trace-formatter
241
+ post_install_message:
242
+ rdoc_options: []
243
+ require_paths:
244
+ - lib
245
+ required_ruby_version: !ruby/object:Gem::Requirement
246
+ requirements:
247
+ - - ">="
248
+ - !ruby/object:Gem::Version
249
+ version: 2.5.0
250
+ required_rubygems_version: !ruby/object:Gem::Requirement
251
+ requirements:
252
+ - - ">="
253
+ - !ruby/object:Gem::Version
254
+ version: '0'
255
+ requirements: []
256
+ rubygems_version: 3.1.6
257
+ signing_key:
258
+ specification_version: 4
259
+ summary: Formatter for RSpec to represent test runs as trace events
260
+ test_files: []