tracing-matchers 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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