File Coverage

blib/lib/OpenTracing/Implementation/Test/Tracer.pm
Criterion Covered Total %
statement 83 104 79.8
branch 5 10 50.0
condition 4 5 80.0
subroutine 22 24 91.6
pod 4 6 66.6
total 118 149 79.1


line stmt bran cond sub pod time code
1             package OpenTracing::Implementation::Test::Tracer;
2              
3             our $VERSION = 'v0.103.1';
4              
5 4     4   341014 use Moo;
  4         19  
  4         24  
6              
7             with 'OpenTracing::Role::Tracer';
8              
9 4     4   1897 use aliased 'OpenTracing::Implementation::Test::Scope';
  4         854  
  4         47  
10 4     4   568 use aliased 'OpenTracing::Implementation::Test::ScopeManager';
  4         11  
  4         30  
11 4     4   452 use aliased 'OpenTracing::Implementation::Test::Span';
  4         14  
  4         24  
12 4     4   544 use aliased 'OpenTracing::Implementation::Test::SpanContext';
  4         44  
  4         30  
13              
14 4     4   514 use Carp qw/croak/;
  4         12  
  4         253  
15 4     4   1673 use PerlX::Maybe qw/maybe/;
  4         8140  
  4         26  
16 4     4   303 use Scalar::Util qw/blessed/;
  4         11  
  4         253  
17 4     4   27 use Test::Builder;
  4         9  
  4         160  
18 4     4   44 use Test::Deep qw/superbagof superhashof cmp_details deep_diag/;
  4         18  
  4         47  
19 4     4   3057 use Tree;
  4         24918  
  4         162  
20 4     4   31 use Types::Standard qw/Str/;
  4         11  
  4         52  
21              
22 4     4   6706 use namespace::clean;
  4         14  
  4         38  
23              
24             use constant {
25 4         8086 HASH_CARRIER_KEY => 'opentracing_context',
26             PREFIX_HTTP => 'OpenTracing-',
27 4     4   1776 };
  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 128 my ($self, $span) = @_;
48 42         75 push @{ $self->spans }, $span;
  42         803  
49 42         297 return;
50             }
51              
52             sub get_spans_as_struct {
53 7     7 1 28 my ($self) = @_;
54 7         14 return map { $self->to_struct($_) } @{ $self->spans };
  28         128  
  7         160  
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 61 my ($class, $span) = @_;
87 28         546 my $context = $span->get_context();
88            
89 28 100       2162 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         12808 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   16 my ($name, $val) = @_;
190              
191 8         20 foreach ($name, $val) {
192 16         36 s/\\/\\\\/g;
193 16         39 s/=/\\=/g;
194             }
195 8         26 return "$name=$val";
196             }
197              
198             sub _decode_baggage_header {
199 8     8   18 my ($header_value) = @_;
200            
201 8         46 my ($name, $val) = split /(?: \\ \\ )* [^\\] \K = /x, $header_value, 2;
202 8         19 foreach ($name, $val) {
203 16         27 s/\\\\/\\/g;
204 16         37 s/\\=/=/g;
205             }
206 8         26 return ($name, $val);
207             }
208              
209              
210             sub _maybe_build_context {
211 21     21   92 my ($self, %args) = @_;
212 21         53 my $trace_id = delete $args{trace_id};
213 21         41 my $span_id = delete $args{span_id};
214 21   100     88 my $baggage_items = delete $args{baggage_items} // {};
215 21 100 66     106 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         79 );
221 18         390 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         9799 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 24 my ($self, $exp, $test_name) = @_;
261 6         37 my $test = Test::Builder->new;
262              
263 6         55 my @spans = $self->get_spans_as_struct;
264 6         34 my ($ok, $stack) = cmp_details(\@spans, $exp);
265 6 50       49509 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         3463 return $ok;
270             }
271              
272             sub cmp_easy {
273 6     6 1 2416 my $exp = $_[1];
274 6         15 $_[1] = superbagof(map { superhashof($_) } @$exp);
  17         252  
275 6         15908 goto &cmp_deeply;
276             }
277              
278             1;
279              
280             __END__
281              
282             =pod
283              
284              
285              
286              
287              
288             =head1 NAME
289              
290             OpenTracing::Implementation::Test::Tracer - OpenTracing Test for Tracer
291              
292              
293              
294             =head1 DESCRIPTION
295              
296             This tracer keeps track of created spans by itself, using an internal structure.
297             It can be used with L<Test::Builder> tests to check the correctness of OpenTracing
298             utilites or to easily inspect your instrumentation.
299              
300              
301              
302             =head1 INSTANCE METHODS
303              
304             =head2 C<get_spans_as_struct>
305              
306             Returns a list of hashes representing all spans, including information from
307             SpanContexts. Example structure:
308              
309             (
310             {
311             operation_name => 'begin',
312             span_id => '7a7da90',
313             trace_id => 'cacbd7a',
314             level => 0,
315             parent_id => undef,
316             has_finished => '',
317             start_time => 1592863360.000000,
318             finish_time => undef,
319             duration => undef,
320             baggage_items => {},
321             tags => { a => 1 },
322             },
323             {
324             operation_name => 'sub',
325             span_id => 'e0be9cc',
326             trace_id => 'cacbd7a'
327             level => 1,
328             parent_id => '7a7da90',
329             has_finished => 1,
330             start_time => 1592863360.000000,
331             finish_time => 1592863360.811969,
332             duration => 0.811956882476807,
333             baggage_items => {},
334             tags => { a => 2 },
335             };
336             )
337              
338              
339              
340             =head2 C<span_tree>
341              
342             Return a string representation of span relationships.
343              
344              
345              
346             =head2 C<cmp_deeply>
347              
348             $tracer->cmp_deeply $all_expected, $test_message;
349              
350             This L<Test::Builder>-enabled test method, will emit a single test with
351             C<$test_message>. The test will compare current saved spans (same as returned by
352             L<get_spans_as_struct>) with C<$all_expected> using C<cmp_deeply> from
353             L<Test::Deep>.
354              
355              
356              
357             =head2 C<cmp_easy>
358              
359             $tracer->cmp_easy $any_expected, $test_message;
360              
361             Same as L<cmp_deeply> but transforms C<$any_expected> into a I<super bag> of
362             I<super hashes> before the comparison, so that not all keys need to be specified
363             and order doesn't matter.
364              
365              
366              
367             =head2 C<clear_spans>
368              
369             Removes all saved spans from the tracer, useful for starting fresh before new
370             test cases.
371              
372              
373              
374             =head1 AUTHOR
375              
376             Szymon Nieznanski <snieznanski@perceptyx.com>
377              
378              
379              
380             =head1 COPYRIGHT AND LICENSE
381              
382             'Test::OpenTracing::Integration'
383             is Copyright (C) 2019 .. 2020, Perceptyx Inc
384              
385             This library is free software; you can redistribute it and/or modify it under
386             the terms of the Artistic License 2.0.
387              
388             This package is distributed in the hope that it will be useful, but it is
389             provided "as is" and without any express or implied warranties.
390              
391             For details, see the full text of the license in the file LICENSE.
392              
393              
394             =cut