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.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -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__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -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