File Coverage

blib/lib/OpenTracing/Implementation/Test/Tracer.pm
Criterion Covered Total %
statement 87 108 80.5
branch 5 10 50.0
condition 4 5 80.0
subroutine 23 25 92.0
pod 4 7 57.1
total 123 155 79.3


line stmt bran cond sub pod time code
1             package OpenTracing::Implementation::Test::Tracer;
2              
3             our $VERSION = 'v0.104.0';
4              
5 4     4   332781 use Moo;
  4         18  
  4         36  
6              
7             with 'OpenTracing::Role::Tracer';
8              
9 4     4   1924 use aliased 'OpenTracing::Implementation::Test::Scope';
  4         795  
  4         27  
10 4     4   545 use aliased 'OpenTracing::Implementation::Test::ScopeManager';
  4         12  
  4         24  
11 4     4   429 use aliased 'OpenTracing::Implementation::Test::Span';
  4         13  
  4         31  
12 4     4   473 use aliased 'OpenTracing::Implementation::Test::SpanContext';
  4         21  
  4         28  
13              
14 4     4   486 use Carp qw/croak/;
  4         10  
  4         224  
15 4     4   1581 use PerlX::Maybe qw/maybe/;
  4         7750  
  4         28  
16 4     4   318 use Scalar::Util qw/blessed/;
  4         9  
  4         190  
17 4     4   28 use Test::Builder;
  4         9  
  4         165  
18 4     4   33 use Test::Deep qw/superbagof superhashof cmp_details deep_diag/;
  4         17  
  4         42  
19 4     4   3114 use Tree;
  4         24878  
  4         150  
20 4     4   30 use Types::Standard qw/Str/;
  4         12  
  4         30  
21              
22 4     4   5948 use namespace::clean;
  4         9  
  4         34  
23              
24             use constant {
25 4         8089 HASH_CARRIER_KEY => 'opentracing_context',
26             PREFIX_HTTP => 'OpenTracing-',
27 4     4   1683 };
  4         10  
28              
29             has '+scope_manager' => (
30             required => 0,
31             default => sub { ScopeManager->new },
32             );
33              
34             has spans => (
35             is => 'rwp',
36             default => sub { [] },
37             lazy => 1,
38             clearer => 1,
39             );
40              
41             has default_context_item => (
42             is => 'ro',
43             isa => Str,
44             );
45              
46             sub register_span {
47 42     42 0 109 my ($self, $span) = @_;
48 42         71 push @{ $self->spans }, $span;
  42         813  
49 42         296 return;
50             }
51              
52             sub get_spans_as_struct {
53 7     7 1 26 my ($self) = @_;
54 7         13 return map { $self->to_struct($_) } @{ $self->spans };
  28         125  
  7         174  
55             }
56              
57             sub span_tree {
58 0     0 1 0 my ($self) = @_;
59              
60 0         0 my @roots;
61 0         0 my %nodes = map { $_->get_span_id() => $self->_tree_node($_) } @{ $self->spans };
  0         0  
  0         0  
62 0         0 foreach my $span (@{ $self->spans }) {
  0         0  
63 0         0 my $node = $nodes{ $span->get_span_id };
64 0         0 my $parent_id = $span->get_parent_span_id;
65              
66 0 0       0 if (defined $parent_id) {
67 0         0 $nodes{$parent_id}->add_child($node);
68             }
69             else {
70 0         0 push @roots, $node;
71             }
72             }
73              
74             return join "\n",
75 0         0 map { @{ $_->tree2string({ no_attributes => 1 }) } } @roots;
  0         0  
  0         0  
76             }
77              
78             sub _tree_node {
79 0     0   0 my ($self, $span) = @_;
80 0         0 my $name = $span->get_operation_name;
81 0 0       0 my $status = $span->has_finished ? $span->duration : '...';
82 0         0 return Tree->new("$name ($status)");
83             }
84              
85             sub to_struct {
86 28     28 0 65 my ($class, $span) = @_;
87 28         530 my $context = $span->get_context();
88            
89 28 100       2091 my $data = {
90             baggage_items => { $context->get_baggage_items },
91             context_item => $context->context_item,
92             has_finished => !!$span->has_finished(),
93             level => $context->level,
94             operation_name => $span->get_operation_name,
95             parent_id => scalar $span->get_parent_span_id(),
96             span_id => $context->span_id,
97             start_time => $span->start_time(),
98             tags => { $span->get_tags },
99             trace_id => $context->trace_id,
100            
101             $span->has_finished() ? ( # these die on unfinished spans
102             duration => $span->duration(),
103             finish_time => $span->finish_time(),
104             ) : (
105             duration => undef,
106             finish_time => undef,
107             ),
108             };
109            
110 28         13194 return $data
111             }
112              
113             sub extract_context_from_hash_reference {
114             my ($self, $carrier) = @_;
115              
116             my $context = $carrier->{ (HASH_CARRIER_KEY) };
117             return $self->_maybe_build_context(%$context);
118             }
119              
120             sub inject_context_into_hash_reference {
121             my ($self, $carrier, $context) = @_;
122              
123             $carrier->{ (HASH_CARRIER_KEY) } = {
124             span_id => $context->span_id,
125             trace_id => $context->trace_id,
126             level => $context->level,
127             context_item => $context->context_item,
128             baggage_items => { $context->get_baggage_items() },
129             };
130             return $carrier;
131             }
132              
133             sub extract_context_from_array_reference {
134             my ($self, $carrier) = @_;
135             return $self->extract_context_from_hash_reference({@$carrier});
136             }
137              
138             sub inject_context_into_array_reference {
139             my ($self, $carrier, $context) = @_;
140              
141             my %hash_carrier;
142             $self->inject_context_into_hash_reference(\%hash_carrier, $context);
143             push @$carrier, %hash_carrier;
144              
145             return $carrier;
146             }
147              
148             sub extract_context_from_http_headers {
149             my ($self, $carrier) = @_;
150              
151             my $trace_id = $carrier->header(PREFIX_HTTP . 'Trace-Id');
152             my $span_id = $carrier->header(PREFIX_HTTP . 'Span-Id');
153             my $level = $carrier->header(PREFIX_HTTP . 'Level');
154             my $context_item = $carrier->header(PREFIX_HTTP . 'ContextItem');
155              
156             my %baggage = map { _decode_baggage_header($_) }
157             $carrier->header( PREFIX_HTTP . 'Baggage' );
158              
159             return $self->_maybe_build_context(
160             trace_id => $trace_id,
161             span_id => $span_id,
162             level => $level,
163             context_item => $context_item,
164             baggage_items => \%baggage,
165             );
166             }
167              
168             sub inject_context_into_http_headers {
169             my ($self, $carrier, $context) = @_;
170            
171             $carrier->header(
172             PREFIX_HTTP . 'Span-Id' => $context->span_id,
173             PREFIX_HTTP . 'Trace-Id' => $context->trace_id,
174             PREFIX_HTTP . 'Level' => $context->level,
175             PREFIX_HTTP . 'ContextItem' => $context->context_item,
176             );
177              
178             my %baggage = $context->get_baggage_items();
179             while (my ($name, $val) = each %baggage) {
180             my $header_field = PREFIX_HTTP . 'Baggage';
181             my $header_value = _encode_baggage_header($name, $val);
182             $carrier->push_header($header_field => $header_value);
183             }
184              
185             return $carrier;
186             }
187              
188             sub _encode_baggage_header {
189 8     8   17 my ($name, $val) = @_;
190              
191 8         18 foreach ($name, $val) {
192 16         34 s/\\/\\\\/g;
193 16         38 s/=/\\=/g;
194             }
195 8         29 return "$name=$val";
196             }
197              
198             sub _decode_baggage_header {
199 8     8   16 my ($header_value) = @_;
200            
201 8         42 my ($name, $val) = split /(?: \\ \\ )* [^\\] \K = /x, $header_value, 2;
202 8         21 foreach ($name, $val) {
203 16         28 s/\\\\/\\/g;
204 16         39 s/\\=/=/g;
205             }
206 8         26 return ($name, $val);
207             }
208              
209              
210             sub _maybe_build_context {
211 21     21   84 my ($self, %args) = @_;
212 21         54 my $trace_id = delete $args{trace_id};
213 21         44 my $span_id = delete $args{span_id};
214 21   100     67 my $baggage_items = delete $args{baggage_items} // {};
215 21 100 66     168 return unless defined $trace_id and defined $span_id;
216              
217             my %context_args = (
218             maybe level => $args{level},
219             maybe context_item => $args{context_item},
220 18         85 );
221 18         411 my $context = $self->build_context(%context_args)
222             ->with_trace_id($trace_id)
223             ->with_span_id($span_id)
224             ->with_baggage_items(%$baggage_items);
225 18         9718 return $context;
226             }
227              
228              
229             sub build_span {
230             my ($self, %opts) = @_;
231              
232             my $child_of = $opts{child_of};
233             my $context = $opts{context};
234             $context = $context->with_next_level if defined $child_of;
235              
236             my $span = Span->new(
237             operation_name => $opts{operation_name},
238             maybe child_of => $child_of,
239             context => $context,
240             start_time => $opts{start_time} // time,
241             tags => $opts{tags} // {},
242             );
243             $self->register_span($span);
244              
245             return $span
246             }
247              
248             sub build_context {
249             my ($self, %opts) = @_;
250             my $context_item = delete $opts{ context_item }
251             || $self->default_context_item;
252              
253             return SpanContext->new(
254             %opts,
255             context_item => $context_item,
256             );
257             }
258              
259             sub cmp_deeply {
260 6     6 1 31 my ($self, $exp, $test_name) = @_;
261 6         34 my $test = Test::Builder->new;
262              
263 6         58 my @spans = $self->get_spans_as_struct;
264 6         28 my ($ok, $stack) = cmp_details(\@spans, $exp);
265 6 50       41982 if (not $test->ok($ok, $test_name)) {
266 0         0 $test->diag(deep_diag($stack));
267 0         0 $test->diag($test->explain(\@spans));
268             }
269 6         2876 return $ok;
270             }
271              
272             sub cmp_easy {
273 5     5 1 2702 my $exp = $_[1];
274 5         13 $_[1] = superbagof(map { superhashof($_) } @$exp);
  10         151  
275 5         7844 goto &cmp_deeply;
276             }
277              
278             sub cmp_spans {
279 1     1 0 22 my $exp = $_[1];
280 1         5 $_[1] = [ map { superhashof($_) } @$exp ];
  7         105  
281 1         24 goto &cmp_deeply;
282             }
283              
284             1;
285              
286             __END__
287              
288             =pod
289              
290              
291              
292              
293              
294             =head1 NAME
295              
296             OpenTracing::Implementation::Test::Tracer - OpenTracing Test for Tracer
297              
298              
299              
300             =head1 DESCRIPTION
301              
302             This tracer keeps track of created spans by itself, using an internal structure.
303             It can be used with L<Test::Builder> tests to check the correctness of OpenTracing
304             utilites or to easily inspect your instrumentation.
305              
306              
307              
308             =head1 INSTANCE METHODS
309              
310             =head2 C<get_spans_as_struct>
311              
312             Returns a list of hashes representing all spans, including information from
313             SpanContexts. Example structure:
314              
315             (
316             {
317             operation_name => 'begin',
318             span_id => '7a7da90',
319             trace_id => 'cacbd7a',
320             level => 0,
321             parent_id => undef,
322             has_finished => '',
323             start_time => 1592863360.000000,
324             finish_time => undef,
325             duration => undef,
326             baggage_items => {},
327             tags => { a => 1 },
328             },
329             {
330             operation_name => 'sub',
331             span_id => 'e0be9cc',
332             trace_id => 'cacbd7a'
333             level => 1,
334             parent_id => '7a7da90',
335             has_finished => 1,
336             start_time => 1592863360.000000,
337             finish_time => 1592863360.811969,
338             duration => 0.811956882476807,
339             baggage_items => {},
340             tags => { a => 2 },
341             };
342             )
343              
344              
345              
346             =head2 C<span_tree>
347              
348             Return a string representation of span relationships.
349              
350              
351              
352             =head2 C<cmp_deeply>
353              
354             $tracer->cmp_deeply $all_expected, $test_message;
355              
356             This L<Test::Builder>-enabled test method, will emit a single test with
357             C<$test_message>. The test will compare current saved spans (same as returned by
358             L<get_spans_as_struct>) with C<$all_expected> using C<cmp_deeply> from
359             L<Test::Deep>.
360              
361              
362              
363             =head2 C<cmp_easy>
364              
365             $tracer->cmp_easy $any_expected, $test_message;
366              
367             Same as L<cmp_deeply> but transforms C<$any_expected> into a I<super bag> of
368             I<super hashes> before the comparison, so that not all keys need to be specified
369             and order doesn't matter.
370              
371              
372              
373             =head2 C<clear_spans>
374              
375             Removes all saved spans from the tracer, useful for starting fresh before new
376             test cases.
377              
378              
379              
380             =head1 AUTHOR
381              
382             Szymon Nieznanski <snieznanski@perceptyx.com>
383              
384              
385              
386             =head1 COPYRIGHT AND LICENSE
387              
388             'Test::OpenTracing::Integration'
389             is Copyright (C) 2019 .. 2020, Perceptyx Inc
390              
391             This library is free software; you can redistribute it and/or modify it under
392             the terms of the Artistic License 2.0.
393              
394             This package is distributed in the hope that it will be useful, but it is
395             provided "as is" and without any express or implied warranties.
396              
397             For details, see the full text of the license in the file LICENSE.
398              
399              
400             =cut