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 5 7 71.4
total 124 155 80.0


line stmt bran cond sub pod time code
1             package OpenTracing::Implementation::Test::Tracer;
2              
3             our $VERSION = 'v0.104.1';
4              
5 4     4   339329 use Moo;
  4         16  
  4         24  
6              
7             with 'OpenTracing::Role::Tracer';
8              
9 4     4   1983 use aliased 'OpenTracing::Implementation::Test::Scope';
  4         801  
  4         38  
10 4     4   473 use aliased 'OpenTracing::Implementation::Test::ScopeManager';
  4         11  
  4         25  
11 4     4   425 use aliased 'OpenTracing::Implementation::Test::Span';
  4         10  
  4         27  
12 4     4   507 use aliased 'OpenTracing::Implementation::Test::SpanContext';
  4         11  
  4         29  
13              
14 4     4   579 use Carp qw/croak/;
  4         11  
  4         262  
15 4     4   1680 use PerlX::Maybe qw/maybe/;
  4         7937  
  4         24  
16 4     4   304 use Scalar::Util qw/blessed/;
  4         12  
  4         231  
17 4     4   25 use Test::Builder;
  4         11  
  4         172  
18 4     4   37 use Test::Deep qw/superbagof superhashof cmp_details deep_diag/;
  4         14  
  4         48  
19 4     4   3122 use Tree;
  4         24661  
  4         152  
20 4     4   31 use Types::Standard qw/Str/;
  4         9  
  4         44  
21              
22 4     4   6290 use namespace::clean;
  4         8  
  4         37  
23              
24             use constant {
25 4         8223 HASH_CARRIER_KEY => 'opentracing_context',
26             PREFIX_HTTP => 'OpenTracing-',
27 4     4   1725 };
  4         11  
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 103 my ($self, $span) = @_;
48 42         80 push @{ $self->spans }, $span;
  42         798  
49 42         310 return;
50             }
51              
52             sub get_spans_as_struct {
53 7     7 1 30 my ($self) = @_;
54 7         14 return map { $self->to_struct($_) } @{ $self->spans };
  28         146  
  7         183  
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 58 my ($class, $span) = @_;
87 28         581 my $context = $span->get_context();
88            
89 28 100       2169 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         12914 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   18 my ($name, $val) = @_;
190              
191 8         19 foreach ($name, $val) {
192 16         33 s/\\/\\\\/g;
193 16         39 s/=/\\=/g;
194             }
195 8         24 return "$name=$val";
196             }
197              
198             sub _decode_baggage_header {
199 8     8   16 my ($header_value) = @_;
200            
201 8         44 my ($name, $val) = split /(?: \\ \\ )* [^\\] \K = /x, $header_value, 2;
202 8         18 foreach ($name, $val) {
203 16         30 s/\\\\/\\/g;
204 16         36 s/\\=/=/g;
205             }
206 8         25 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         43 my $span_id = delete $args{span_id};
214 21   100     81 my $baggage_items = delete $args{baggage_items} // {};
215 21 100 66     110 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         83 );
221 18         385 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         9923 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 21 my ($self, $exp, $test_name) = @_;
261 6         40 my $test = Test::Builder->new;
262              
263 6         61 my @spans = $self->get_spans_as_struct;
264 6         24 my ($ok, $stack) = cmp_details(\@spans, $exp);
265 6 50       35306 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         4041 return $ok;
270             }
271              
272             sub cmp_easy {
273 5     5 1 2532 my $exp = $_[1];
274 5         14 $_[1] = superbagof(map { superhashof($_) } @$exp);
  10         152  
275 5         7395 goto &cmp_deeply;
276             }
277              
278             sub cmp_spans {
279 1     1 1 29 my $exp = $_[1];
280 1         5 $_[1] = [ map { superhashof($_) } @$exp ];
  7         107  
281 1         31 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<cmp_spans>
374              
375             $tracer->cmp_spans $all_expected, $test_message;
376              
377             Same as L<cmp_deeply> but transforms C<$all_expected> into a array reference of
378             I<super hashes> before the comparison, so that all spans are to be expected in
379             the given order, but not not all keys need to be specified.
380              
381              
382              
383             =head2 C<clear_spans>
384              
385             Removes all saved spans from the tracer, useful for starting fresh before new
386             test cases.
387              
388              
389              
390             =head1 AUTHOR
391              
392             Szymon Nieznanski <snieznanski@perceptyx.com>
393              
394              
395              
396             =head1 COPYRIGHT AND LICENSE
397              
398             'Test::OpenTracing::Integration'
399             is Copyright (C) 2019 .. 2020, Perceptyx Inc
400              
401             This library is free software; you can redistribute it and/or modify it under
402             the terms of the Artistic License 2.0.
403              
404             This package is distributed in the hope that it will be useful, but it is
405             provided "as is" and without any express or implied warranties.
406              
407             For details, see the full text of the license in the file LICENSE.
408              
409              
410             =cut