tracing-matchers 1.0.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +10 -0
- data/LICENSE +201 -0
- data/README.md +169 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/tracing/matchers.rb +155 -0
- data/lib/tracing/matchers/have_span.rb +101 -0
- data/lib/tracing/matchers/have_spans.rb +80 -0
- data/lib/tracing/matchers/have_traces.rb +89 -0
- data/lib/tracing/matchers/span/be_child_of.rb +99 -0
- data/lib/tracing/matchers/span/have_baggage.rb +50 -0
- data/lib/tracing/matchers/span/have_log.rb +51 -0
- data/lib/tracing/matchers/span/have_tag.rb +50 -0
- data/lib/tracing/matchers/span_matchers.rb +72 -0
- data/lib/tracing/matchers/version.rb +5 -0
- data/tracing-matchers.gemspec +29 -0
- metadata +122 -0
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "tracing/matchers"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require "tracing/matchers/version"
|
2
|
+
require "tracing/matchers/span_matchers"
|
3
|
+
require "tracing/matchers/have_traces"
|
4
|
+
require "tracing/matchers/have_spans"
|
5
|
+
require "tracing/matchers/have_span"
|
6
|
+
|
7
|
+
module Tracing
|
8
|
+
module Matchers
|
9
|
+
# The `have_traces` matcher tests that the tracer traced any or a specific
|
10
|
+
# number of **traces**.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
#
|
14
|
+
# # Passes if tracer traced any trace
|
15
|
+
# expect(tracer).to have_spans
|
16
|
+
#
|
17
|
+
# # Passes if tracer traced exactly 10 distinct traces
|
18
|
+
# expect(tracer).to have_spans(10)
|
19
|
+
#
|
20
|
+
# # Passes if tracer started any trace
|
21
|
+
# expect(tracer).to have_spans.started
|
22
|
+
#
|
23
|
+
# # Passes if tracer has any finished traces
|
24
|
+
# expect(tracer).to have_spans.finished
|
25
|
+
#
|
26
|
+
# @note It's possible to chain `started` and `finished` at the same time.
|
27
|
+
# Expect that only the last method will be applied e.g. `have_spans.finished.started`
|
28
|
+
# will result in application of `started`.
|
29
|
+
#
|
30
|
+
# @param [Fixnum] n expected number of distinct traces
|
31
|
+
# @return [HaveTraces]
|
32
|
+
#
|
33
|
+
# @see HaveTraces#started
|
34
|
+
# @see HaveTraces#finished
|
35
|
+
def have_traces(n = anything)
|
36
|
+
Tracing::Matchers::HaveTraces.new(n)
|
37
|
+
end
|
38
|
+
|
39
|
+
# The `have_spans` matcher tests that the tracer traced any or a specific
|
40
|
+
# number of **spans**.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
#
|
44
|
+
# # Passes if either `tracer.spans`, or `tracer.finished_spans` includes any span
|
45
|
+
# expect(tracer).to have_spans
|
46
|
+
#
|
47
|
+
# # Passes if either `tracer.spans`, or `tracer.finished_spans` includes exactly 10 spans
|
48
|
+
# expect(tracer).to have_spans(10)
|
49
|
+
#
|
50
|
+
# # Passes if `tracer.spans` includes any span
|
51
|
+
# expect(tracer).to have_spans.started
|
52
|
+
#
|
53
|
+
# # Passes if `tracer.finished_spans` includes any span
|
54
|
+
# expect(tracer).to have_spans.finished
|
55
|
+
#
|
56
|
+
# @note It's possible to chain `started` and `finished` at the same time.
|
57
|
+
# Expect that only the last method will be applied e.g. `have_spans.finished.started`
|
58
|
+
# will result in application of `started`.
|
59
|
+
#
|
60
|
+
# @param [Fixnum] n expected number of traced spans
|
61
|
+
# @return [HaveSpans]
|
62
|
+
#
|
63
|
+
# @see HaveSpans#started
|
64
|
+
# @see HaveSpans#finished
|
65
|
+
def have_spans(n = anything)
|
66
|
+
Tracing::Matchers::HaveSpans.new(n)
|
67
|
+
end
|
68
|
+
|
69
|
+
# The `have_span` matcher tests that the tracer traced a span matching a criteria.
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
#
|
73
|
+
# # Behaves very similar to have_traces without any arguments.
|
74
|
+
# expect(tracer).to have_span
|
75
|
+
#
|
76
|
+
# # Passes if the tracer traced span with the operation name "User Load".
|
77
|
+
# expect(tracer).to have_span("User Load")
|
78
|
+
#
|
79
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
80
|
+
# # which is still in progress.
|
81
|
+
# expect(tracer).to have_span("User Load").in_progress
|
82
|
+
#
|
83
|
+
# # Same as above
|
84
|
+
# expect(tracer).to have_span("User Load").started
|
85
|
+
#
|
86
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
87
|
+
# # which has finished.
|
88
|
+
# expect(tracer).to have_span("User Load").finished
|
89
|
+
#
|
90
|
+
# # Passes if the tracer traced any span which has any tag
|
91
|
+
# expect(tracer).to have_span.with_tags
|
92
|
+
#
|
93
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
94
|
+
# # and which has any tag
|
95
|
+
# expect(tracer).to have_span("User Load").with_tags
|
96
|
+
#
|
97
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
98
|
+
# # and which has a tag 'component' => 'ActiveRecord'.
|
99
|
+
# expect(tracer).to have_span("User Load").with_tags('component' => 'ActiveRecord')
|
100
|
+
#
|
101
|
+
# # Passes if the tracer traced any span which has any log entry
|
102
|
+
# expect(tracer).to have_span.with_logs
|
103
|
+
#
|
104
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
105
|
+
# # and which has a log entry with event 'error'.
|
106
|
+
# expect(tracer).to have_span("User Load").with_logs(event: 'error')
|
107
|
+
#
|
108
|
+
# # Passes if the tracer traced any span which has any entry in a baggage
|
109
|
+
# expect(tracer).to have_span.with_baggae
|
110
|
+
#
|
111
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
112
|
+
# # and which has a baggage with 'account_id' => '1'
|
113
|
+
# expect(tracer).to have_span("User Load").with_baggae('account_id' => '1')
|
114
|
+
#
|
115
|
+
# # Passes if the tracer traced any span which has a parent
|
116
|
+
# expect(tracer).to have_span.with_parent
|
117
|
+
#
|
118
|
+
# # Passes if the tracer traced any span which has root_span as a parent
|
119
|
+
# expect(tracer).to have_span.child_of(root_span)
|
120
|
+
#
|
121
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
122
|
+
# # and which has a parent span with operation name "Authentication".
|
123
|
+
# expect(tracer).to have_span("User Load").child_of("Authentication")
|
124
|
+
#
|
125
|
+
# # Passes if the tracer traced span with the operation name "User Load"
|
126
|
+
# # which has a tag 'component' => 'ActiveRecord',
|
127
|
+
# # has any log entry,
|
128
|
+
# # has a baggage with 'account_id' => '1',
|
129
|
+
# # and is child of a span with operation name "Authentication".
|
130
|
+
# expect(tracer).to have_span("User Load")
|
131
|
+
# .with_tags('component' => 'ActiveRecord')
|
132
|
+
# .with_logs
|
133
|
+
# .with_baggage('account_id' => '1')
|
134
|
+
# .child_of("Authentication")
|
135
|
+
#
|
136
|
+
# @param [String] operation_name operation name of a searched span
|
137
|
+
# @return [HaveSpan]
|
138
|
+
#
|
139
|
+
# @see HaveSpan#in_progress
|
140
|
+
# @see HaveSpan#started
|
141
|
+
# @see HaveSpan#finished
|
142
|
+
# @see HaveSpan#with_tags
|
143
|
+
# @see HaveSpan#with_logs
|
144
|
+
# @see HaveSpan#with_baggage
|
145
|
+
# @see HaveSpan#with_parent
|
146
|
+
# @see HaveSpan#child_of
|
147
|
+
def have_span(operation_name = anything)
|
148
|
+
Tracing::Matchers::HaveSpan.new(operation_name)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
RSpec.configure do |config|
|
154
|
+
config.include Tracing::Matchers
|
155
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Tracing
|
2
|
+
module Matchers
|
3
|
+
# @private
|
4
|
+
class HaveSpan
|
5
|
+
def initialize(operation_name)
|
6
|
+
@expected = operation_name
|
7
|
+
@predicates = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def in_progress
|
11
|
+
@state_predicate = RSpec::Matchers::BuiltIn::BePredicate.new(:be_started)
|
12
|
+
self
|
13
|
+
end
|
14
|
+
alias :started :in_progress
|
15
|
+
|
16
|
+
def finished
|
17
|
+
@state_predicate = RSpec::Matchers::BuiltIn::BePredicate.new(:be_finished)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_tag(*args)
|
22
|
+
@predicates << Tracing::Matchers::Span::HaveTag.new(*args)
|
23
|
+
self
|
24
|
+
end
|
25
|
+
alias :with_tags :with_tag
|
26
|
+
|
27
|
+
def with_log(**fields)
|
28
|
+
@predicates << Tracing::Matchers::Span::HaveLog.new(**fields)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
alias :with_logs :with_log
|
32
|
+
|
33
|
+
def with_baggage(*args)
|
34
|
+
@predicates << Tracing::Matchers::Span::HaveBaggage.new(*args)
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def child_of(parent = :any)
|
39
|
+
@predicates << Tracing::Matchers::Span::BeChildOf.new(parent)
|
40
|
+
self
|
41
|
+
end
|
42
|
+
alias :with_parent :child_of
|
43
|
+
|
44
|
+
# @return [Boolean]
|
45
|
+
def matches?(tracer)
|
46
|
+
@subject = tracer
|
47
|
+
@actual = actual_spans
|
48
|
+
|
49
|
+
@actual.any? { |span| all_predicates.all? { |matcher| matcher.matches?(span) } }
|
50
|
+
end
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def description
|
54
|
+
"have a#{expected_span_description}"
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
def failure_message
|
59
|
+
"expected a#{expected_span_description}"
|
60
|
+
end
|
61
|
+
alias :failure_message_for_should :failure_message
|
62
|
+
|
63
|
+
# @return [String]
|
64
|
+
def failure_message_when_negated
|
65
|
+
"did not expect#{expected_span_description}"
|
66
|
+
end
|
67
|
+
alias :failure_message_for_should_not :failure_message_when_negated
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def expected_span_description
|
72
|
+
desc = ""
|
73
|
+
desc << " #{@state_predicate.description.gsub('be ', '')}" if state_predicate?
|
74
|
+
desc << " span"
|
75
|
+
desc << " with operation name \"#{@expected}\"" if operation_name?
|
76
|
+
@predicates.each { |matcher| desc << " #{matcher.description.gsub('have', 'with')}" }
|
77
|
+
desc
|
78
|
+
end
|
79
|
+
|
80
|
+
def actual_spans
|
81
|
+
@subject.spans.select(&expected_predicate)
|
82
|
+
end
|
83
|
+
|
84
|
+
def expected_predicate
|
85
|
+
operation_name? ? -> (span) { span.operation_name == @expected } : -> (_) { true }
|
86
|
+
end
|
87
|
+
|
88
|
+
def operation_name?
|
89
|
+
@expected.is_a?(String)
|
90
|
+
end
|
91
|
+
|
92
|
+
def state_predicate?
|
93
|
+
@state_predicate
|
94
|
+
end
|
95
|
+
|
96
|
+
def all_predicates
|
97
|
+
@all_predicates = (@predicates + [@state_predicate]).compact
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Tracing
|
2
|
+
module Matchers
|
3
|
+
# @private
|
4
|
+
class HaveSpans
|
5
|
+
def initialize(n)
|
6
|
+
@expected = n
|
7
|
+
end
|
8
|
+
|
9
|
+
def started
|
10
|
+
@state = :started
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def finished
|
15
|
+
@state = :finished
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Boolean]
|
20
|
+
def matches?(tracer)
|
21
|
+
@subject = tracer
|
22
|
+
|
23
|
+
if exactly?
|
24
|
+
@actual = spans.size
|
25
|
+
@actual == @expected
|
26
|
+
else
|
27
|
+
spans.any?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
def description
|
33
|
+
desc = "have "
|
34
|
+
desc << "exactly #{@expected} " if exactly?
|
35
|
+
desc << "spans #{@state}"
|
36
|
+
desc.strip
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
def failure_message
|
41
|
+
if exactly?
|
42
|
+
message = "expected #{@expected} spans"
|
43
|
+
message << " #{@state}" if @state
|
44
|
+
message << ", got #{@actual}"
|
45
|
+
message
|
46
|
+
else
|
47
|
+
"expected any span #{@state}".strip
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias :failure_message_for_should :failure_message
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def failure_message_when_negated
|
54
|
+
if exactly?
|
55
|
+
message = "did not expect #{@expected} spans"
|
56
|
+
message << " #{@state}" if @state
|
57
|
+
message << ", got #{@actual}"
|
58
|
+
message
|
59
|
+
else
|
60
|
+
"did not expect spans #{@state}".strip
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias :failure_message_for_should_not :failure_message_when_negated
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def state
|
68
|
+
@state || :started
|
69
|
+
end
|
70
|
+
|
71
|
+
def spans
|
72
|
+
state == :finished ? @subject.finished_spans : @subject.spans
|
73
|
+
end
|
74
|
+
|
75
|
+
def exactly?
|
76
|
+
@expected.is_a?(Fixnum)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Tracing
|
2
|
+
module Matchers
|
3
|
+
# @private
|
4
|
+
class HaveTraces
|
5
|
+
def initialize(n)
|
6
|
+
@expected = n
|
7
|
+
end
|
8
|
+
|
9
|
+
def started
|
10
|
+
@state = :started
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def finished
|
15
|
+
@state = :finished
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Boolean]
|
20
|
+
def matches?(tracer)
|
21
|
+
@subject = tracer
|
22
|
+
|
23
|
+
if exactly?
|
24
|
+
@actual = traces.size
|
25
|
+
@actual == @expected
|
26
|
+
else
|
27
|
+
traces.any?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [String]
|
32
|
+
def description
|
33
|
+
desc = "have "
|
34
|
+
desc << "exactly #{@expected} " if exactly?
|
35
|
+
desc << "traces #{@state}"
|
36
|
+
desc.strip
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String]
|
40
|
+
def failure_message
|
41
|
+
if exactly?
|
42
|
+
message = "expected #{@expected} traces"
|
43
|
+
message << " #{@state}" if @state
|
44
|
+
message << ", got #{@actual}"
|
45
|
+
message
|
46
|
+
else
|
47
|
+
"expected any trace #{@state}".strip
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias :failure_message_for_should :failure_message
|
51
|
+
|
52
|
+
# @return [String]
|
53
|
+
def failure_message_when_negated
|
54
|
+
if exactly?
|
55
|
+
message = "did not expect #{@expected} traces"
|
56
|
+
message << " #{@state}" if @state
|
57
|
+
message << ", got #{@actual}"
|
58
|
+
message
|
59
|
+
else
|
60
|
+
"did not expect traces #{@state}".strip
|
61
|
+
end
|
62
|
+
end
|
63
|
+
alias :failure_message_for_should_not :failure_message_when_negated
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def state
|
68
|
+
@state || :started
|
69
|
+
end
|
70
|
+
|
71
|
+
def exactly?
|
72
|
+
@expected.is_a?(Fixnum)
|
73
|
+
end
|
74
|
+
|
75
|
+
def traces
|
76
|
+
@subject.spans
|
77
|
+
.group_by { |span| span.context.trace_id }
|
78
|
+
.map { |trace_id, spans| Trace.new(trace_id, spans) }
|
79
|
+
.select { |trace| @state == :finished ? trace.finished? : true }
|
80
|
+
end
|
81
|
+
|
82
|
+
class Trace < Struct.new(:trace_id, :spans)
|
83
|
+
def finished?
|
84
|
+
spans.all? { |span| !span.in_progress? }
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|