File Coverage

blib/lib/Tapper/Reports/DPath.pm
Criterion Covered Total %
statement 117 146 80.1
branch 40 64 62.5
condition 11 23 47.8
subroutine 22 25 88.0
pod 7 7 100.0
total 197 265 74.3


line stmt bran cond sub pod time code
1             ## no critic (RequireUseStrict)
2             package Tapper::Reports::DPath;
3             # git description: v5.0.1-2-g176ea62
4              
5             our $AUTHORITY = 'cpan:TAPPER';
6             # ABSTRACT: Tapper - Extended DPath functionality for Tapper reports
7             $Tapper::Reports::DPath::VERSION = '5.0.2';
8 7     7   164358 use 5.010;
  7         21  
9 7     7   1802 use Moose;
  7         1204066  
  7         71  
10              
11 7     7   37321 use Tapper::Model 'model', 'get_hardware_overview'; #, 'get_systems_id_for_hostname'
  7         4399363  
  7         556  
12 7     7   4588 use Text::Balanced 'extract_codeblock';
  7         80467  
  7         465  
13 7     7   2961 use Data::DPath::Path;
  7         227779  
  7         210  
14 7     7   45 use Data::Dumper;
  7         11  
  7         275  
15 7     7   3470 use CHI;
  7         286393  
  7         351  
16              
17             our $puresqlabstract = 0;
18              
19 7         61 use Sub::Exporter -setup => { exports => [ 'reportdata' ],
20             groups => { all => [ 'reportdata' ] },
21 7     7   51 };
  7         10  
22              
23             sub _extract_condition_and_part {
24 23     23   5872 my ($reports_path) = @_;
25 23         111 my ($condition, $path) = extract_codeblock($reports_path, '{}');
26 23         5898 $path =~ s/^\s*::\s*//;
27 23         68 return ($condition, $path);
28             }
29              
30             # better use alias
31 0     0 1 0 sub rds($) { reports_dpath_search(@_) } ## no critic (ProhibitSubroutinePrototypes)
32              
33             # better use alias
34 16     16 1 18150 sub reportdata($) { reports_dpath_search(@_) } ## no critic (ProhibitSubroutinePrototypes)
35              
36             # allow trivial better readable column names
37             # - foo => 23 ... mapped to "me.foo" => 23
38             # - "report.foo" => 23 ... mapped to "me.foo" => 23
39             # - suite_name => "bar" ... mapped to "suite.name" => "bar"
40             # - -and => ... ... mapped to "-and" => ... # just to ensure that it doesn't produce: "me.-and" => ...
41             sub _fix_condition
42             {
43 7     7   2935 no warnings 'uninitialized';
  7         11  
  7         8190  
44 55     55   1081 my $SQLKEYWORDS = 'like|-in|-and|-or';
45 55         88 my ($condition) = @_;
46             # joined suite
47 55         185 $condition =~ s/(['"])?\bsuite_name\b(['"])?\s*=>/"suite.name" =>/; # ';
48 55         111 $condition =~ s/(['"])?\breportgroup_testrun_id\b(['"])?\s*=>/"reportgrouptestrun.testrun_id" =>/; # ';
49 55         106 $condition =~ s/(['"])?\breportgroup_arbitrary_id\b(['"])?\s*=>/"reportgrouparbitrary.arbitrary_id" =>/; # ';
50 55         987 $condition =~ s/([^-\w])(['"])?((report|me)\.)?(?<!suite\.)(?<!reportgrouparbitrary\.)(?<!reportgrouptestrun\.)(?!$SQLKEYWORDS)(\w+)\b(['"])?(\s*)=>/$1"me.$5" =>/; # ';
51              
52 55         219 return $condition;
53              
54             }
55              
56             # ===== CACHE =====
57              
58             # ----- cache complete Tapper::Reports::DPath queries -----
59              
60             sub _cachekey_whole_dpath {
61 0     0   0 my ($reports_path) = @_;
62 0   0     0 my $key = ($ENV{TAPPER_DEVELOPMENT} || "0") . '::' . $reports_path;
63 0         0 return $key;
64             }
65              
66             sub cache_whole_dpath {
67 16     16 1 34 my ($reports_path, $rs_count, $res) = @_;
68              
69 16 50       56 return if $ENV{HARNESS_ACTIVE};
70              
71 0         0 my $cache = CHI->new( driver => 'File',
72             root_dir => '/tmp/cache/dpath',
73             serializer => 'Data::Dumper',
74             compress => 1,
75             );
76              
77 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
78              
79             # we cache on the dpath
80             # but need count to verify and maintain cache validity
81              
82             # say STDERR " -> set whole: $reports_path ($rs_count)";
83 0         0 $cache->set( _cachekey_whole_dpath($reports_path),
84             {
85             count => $rs_count,
86             res => $res,
87             });
88             }
89              
90             sub cached_whole_dpath {
91 16     16 1 34 my ($reports_path, $rs_count) = @_;
92              
93 16 50       65 return if $ENV{HARNESS_ACTIVE};
94              
95 0         0 my $cache = CHI->new( driver => 'File',
96             root_dir => '/tmp/cache/dpath',
97             serializer => 'Data::Dumper',
98             compress => 1,
99             );
100 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
101 0         0 my $cached_res = $cache->get( _cachekey_whole_dpath($reports_path) );
102              
103 0   0     0 my $cached_res_count = $cached_res->{count} || 0;
104             # say STDERR " <- get whole: $reports_path ($rs_count vs. $cached_res_count)";
105 0 0       0 return if not defined $cached_res;
106              
107 0 0       0 if ($cached_res_count == $rs_count) {
108             # say STDERR " Gotcha!";
109             return $cached_res->{res}
110 0         0 }
111              
112             # clean up when matching report count changed
113 0         0 $cache->remove( $reports_path );
114 0         0 return;
115             }
116              
117             # ----- cache single report dpaths queries -----
118              
119             sub _cachekey_single_dpath {
120 0     0   0 my ($path, $reports_id) = @_;
121 0   0     0 my $key = ($ENV{TAPPER_DEVELOPMENT} || "0") . '::' . $reports_id."::".$path;
122             #say STDERR " . $key";
123 0         0 return $key;
124             }
125              
126             sub cache_single_dpath {
127 34     34 1 75 my ($path, $reports_id, $res) = @_;
128              
129 34 50       157 return if $ENV{HARNESS_ACTIVE};
130              
131 0         0 my $cache = CHI->new( driver => 'File',
132             root_dir => '/tmp/cache/dpath',
133             serializer => 'Data::Dumper',
134             compress => 1,
135             );
136 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
137 0         0 $cache->set( _cachekey_single_dpath( $path, $reports_id ),
138             $res
139             );
140             }
141              
142             sub cached_single_dpath {
143 34     34 1 66 my ($path, $reports_id) = @_;
144              
145 34 50       129 return if $ENV{HARNESS_ACTIVE};
146              
147 0         0 my $cache = CHI->new( driver => 'File',
148             root_dir => '/tmp/cache/dpath',
149             serializer => 'Data::Dumper',
150             compress => 1,
151             );
152 0 0       0 $cache->clear() if -e '/tmp/TAPPER_CACHE_CLEAR';
153 0         0 my $cached_res = $cache->get( _cachekey_single_dpath( $path, $reports_id ));
154              
155             # print STDERR " <- get single: $reports_id -- $path: ".Dumper($cached_res);
156 0         0 return $cached_res;
157             }
158              
159             # ===== the query search =====
160              
161             sub reports_dpath_search($) { ## no critic (ProhibitSubroutinePrototypes)
162 16     16 1 37 my ($reports_path) = @_;
163              
164 16         60 my ($condition, $path) = _extract_condition_and_part($reports_path);
165 16         106 my $dpath = new Data::DPath::Path( path => $path );
166 16 50       3577 $condition = _fix_condition($condition) unless $puresqlabstract;
167 16 100       50 my %condition = $condition ? %{ eval $condition } : (); ## no critic (ProhibitStringyEval)
  15         727  
168 16         91 my $rs = model('TestrunDB')->resultset('Report')->search
169             (
170             {
171             %condition
172             },
173             {
174             order_by => 'me.id asc',
175             columns => [ qw(
176             id
177             suite_id
178             suite_version
179             reportername
180             peeraddr
181             peerport
182             peerhost
183             successgrade
184             total
185             failed
186             parse_errors
187             passed
188             skipped
189             todo
190             todo_passed
191             success_ratio
192             starttime_test_program
193             endtime_test_program
194             machine_name
195             machine_description
196             created_at
197             updated_at
198             )],
199             join => [ 'suite', 'reportgrouptestrun', 'reportgrouparbitrary' ],
200             '+select' => [ 'suite.name', 'reportgrouptestrun.testrun_id', 'reportgrouparbitrary.arbitrary_id'],
201             '+as' => [ 'suite.name', 'reportgrouptestrun.testrun_id', 'reportgrouparbitrary.arbitrary_id'],
202             }
203             );
204 16         32330 my $rs_count = $rs->count();
205 16         141074 my @res = ();
206              
207             # layer 2 cache
208 16         61 my $cached_res = cached_whole_dpath( $reports_path, $rs_count );
209 16 50       60 return @$cached_res if defined $cached_res;
210              
211 16         64 while (my $row = $rs->next)
212             {
213 34         231730 my $report_id = $row->id;
214             # layer 1 cache
215              
216 34         363 my $cached_row_res = cached_single_dpath( $path, $report_id );
217              
218 34 50       126 if (defined $cached_row_res) {
219 0         0 push @res, @$cached_row_res;
220 0         0 next;
221             }
222              
223 34         106 my $data = _as_data($row);
224 34         222 my @row_res = $dpath->match( $data );
225              
226 34         57404 cache_single_dpath($path, $report_id, \@row_res);
227              
228 34         1958 push @res, @row_res;
229             }
230              
231 16         2060 cache_whole_dpath($reports_path, $rs_count, \@res);
232              
233 16         57 return @res;
234             }
235              
236             sub _dummy_needed_for_tests {
237             # once there were problems with eval
238 1     1   103 return eval "12345"; ## no critic (ProhibitStringyEval)
239             }
240              
241             sub _groupcontext {
242 36     36   871798 my ($report) = @_;
243              
244 36         99 my %groupcontext = ();
245 36         845 my $id = $report->id;
246 36         899 my $rga = $report->reportgrouparbitrary;
247 36         2695 my $rgt = $report->reportgrouptestrun;
248 36 100       522 my %groupreports = (
    100          
    100          
    100          
249             arbitrary => $rga ? scalar $rga->groupreports : undef,
250             arbitrary_id => $rga ? $rga->arbitrary_id : undef,
251             testrun => $rgt ? scalar $rgt->groupreports : undef,
252             testrun_id => $rgt ? $rgt->testrun_id : undef,
253             );
254              
255             # if ($report->reportgrouptestrun) {
256             # my $rgt_id = $report->reportgrouptestrun->testrun_id;
257             # my $rgt_reports = model('TestrunDB')->resultset('ReportgroupTestrun')->search({ testrun_id => $rgt_id});
258             # # say STDERR "\nrgt $rgt_id count: ", $rgt_reports->count;
259             # }
260              
261 36         104957 foreach my $type (qw(arbitrary testrun))
262             {
263 72 100       3179 next unless $groupreports{$type};
264 45         130 my $group_id = $groupreports{"${type}_id"};
265              
266             # say STDERR "${type}_id: ", $groupreports{"${type}_id"};
267             # say STDERR " groupreports{$type}.count: ", $groupreports{$type}->count;
268             # say STDERR "* $id - groupreports{$type}.count: ", $groupreports{$type}->count;
269 45         148 while (my $groupreport = $groupreports{$type}->next)
270             {
271 120         152791 my $groupreport_id = $groupreport->id;
272             # say STDERR " gr.id: $groupreport_id";
273 120         1098 my @greportsection_meta = ();
274 120         1874 my $grsections = $groupreport->reportsections;
275             # say STDERR "* $groupreport_id GROUPREPORT_SECTIONS count: ", $grsections->count;
276 120         106173 while (my $section = $grsections->next)
277             {
278 180         181417 my %columns = $section->get_columns;
279 180         4340 foreach (keys %columns) {
280 8100 100       10473 delete $columns{$_} unless defined $columns{$_};
281             }
282 180         712 delete $columns{$_} foreach qw(succession name id report_id);
283 180 50       4257 push @greportsection_meta, {
284             $section->name => {
285             %columns
286             }
287             }
288             if keys %columns;
289             }
290 120         262993 my $primary = 0;
291 120 50 66     999 $primary = 1 if $type eq "arbitrary" && $groupreport->reportgrouparbitrary->primaryreport;
292 120 100 100     69014 $primary = 1 if $type eq "testrun" && $groupreport->reportgrouptestrun->primaryreport;
293              
294 120 100       208352 $groupcontext{$type}{$group_id}{$groupreport_id}{myself} = $groupreport_id == $id ? 1 : 0;
295 120 100       372 $groupcontext{$type}{$group_id}{$groupreport_id}{primary} = $primary ? 1 : 0;
296 120         472 $groupcontext{$type}{$group_id}{$groupreport_id}{meta} = \@greportsection_meta;
297             }
298             }
299              
300             # say STDERR Dumper(\%groupcontext);
301 36         4892 return \%groupcontext;
302             }
303              
304             sub _reportgroupstats {
305 36     36   66 my ($report) = @_;
306              
307 36         728 my $rgt = $report->reportgrouptestrun;
308 36         7422 my $reportgroupstats = {};
309              
310             # create report group stats
311 36 100 66     543 if ($report->reportgrouptestrun and $report->reportgrouptestrun->testrun_id)
312             {
313 30         1539 my $rgt_stats = model('TestrunDB')->resultset('ReportgroupTestrunStats')->find($rgt->testrun_id);
314 30 100 66     62701 unless ($rgt_stats and $rgt_stats->testrun_id)
315             {
316             # This is just a fail-back mechanism, in case the "fix-missinging-groupstats" script has not yet been run.
317 3         47 $rgt_stats = model('TestrunDB')->resultset('ReportgroupTestrunStats')->new({ testrun_id => $rgt->testrun_id});
318 3         1105 $rgt_stats->update_failed_passed;
319 3         31570 $rgt_stats->insert;
320             }
321 30         34851 my @stat_fields = (qw/failed passed total parse_errors skipped todo todo_passed success_ratio/);
322 7     7   71 no strict 'refs'; ## no critic (ProhibitNoStrict)
  7         9  
  7         1549  
323             $reportgroupstats = {
324 30         69 map { ($_ => $rgt_stats->$_ ) } @stat_fields
  240         5135  
325             };
326             }
327 36         943 return $reportgroupstats;
328             }
329              
330             sub _as_data
331             {
332 36     36   1121195 my ($report) = @_;
333              
334 36         65 my $hwdb;
335 36 50       179 if (my $host = model('TestrunDB')->resultset("Host")->search({name => $report->machine_name}, {rows => 1})->first) {
336 0         0 $hwdb = get_hardware_overview($host->id);
337             }
338 36 50 33     90812 my %hardwaredb_overview = (defined($hwdb) and %$hwdb) ? (hardwaredb => $hwdb) : ();
339              
340 36         2935 my $reportgroupstats = _reportgroupstats($report);
341              
342 36 50 50     262 my $simple_hash = {
    100          
    100          
343             report => {
344             $report->get_columns,
345             suite_name => $report->suite ? $report->suite->name : 'unknown',
346             reportgroup_testrun_id => $report->reportgrouptestrun ? $report->reportgrouptestrun->testrun_id : undef,
347             reportgroup_arbitrary_id => $report->reportgrouparbitrary ? $report->reportgrouparbitrary->arbitrary_id : undef,
348             machine_name => $report->machine_name || 'unknown',
349             created_at_ymd_hms => $report->created_at->ymd('-')." ".$report->created_at->hms(':'),
350             created_at_ymd => $report->created_at->ymd('-'),
351             %hardwaredb_overview,
352             groupstats => {
353             DEPRECATED => 'BETTER_USE_groupstats_FROM_ONE_LEVEL_ABOVE',
354             %$reportgroupstats,
355             },
356             },
357             results => $report->get_cached_tapdom,
358             groupcontext => _groupcontext($report),
359             groupstats => $reportgroupstats,
360             };
361 36         3480 return $simple_hash;
362             }
363             1;
364              
365             __END__
366              
367             =pod
368              
369             =encoding UTF-8
370              
371             =head1 NAME
372              
373             Tapper::Reports::DPath - Tapper - Extended DPath functionality for Tapper reports
374              
375             =head1 SYNOPSIS
376              
377             use Tapper::Reports::DPath 'reports_dpath_search';
378             # the first bogomips entry of math sections:
379             @resultlist = reportdata (
380             '{ suite_name => "TestSuite-LmBench" } :: /tap/section/math/*/bogomips[0]'
381             );
382             # all report IDs of suite_id 17 that FAILed:
383             @resultlist = reportdata (
384             '{ suite_name => "TestSuite-LmBench" } :: /suite_id[value == 17]/../successgrade[value eq 'FAIL']/../id'
385             );
386              
387             #
388             # '{ "reportgrouptestrun.testrun_id" => 4711 } :: /suite_id[value == 17]/../successgrade[value eq 'FAIL']/../id
389             #
390             # '{ "reportgrouparbitrary.arbitrary_id" => "fc123a2" } :: /suite_id[value == 17]/../successgrade[value eq 'FAIL']/../id
391              
392             This searches all reports of the test suite "TestSuite-LmBench" and
393             furthermore in them for a TAP section "math" with the particular
394             subtest "bogomips" and takes the first array entry of them.
395              
396             The part before the '::' selects reports to search in a DBIx::Class
397             search query, the second part is a normal L<Data::DPath|Data::DPath>
398             expression that matches against the datastructure that is build from
399             the DB.
400              
401             =head1 API FUNCTIONS
402              
403             =head2 reports_dpath_search
404              
405             Takes an extended DPath expression, applies it to Tapper Reports
406             with TAP::DOM structure and returns the matching results in an array.
407              
408             =head2 rds
409              
410             Alias for reports_dpath_search.
411              
412             =head2 reportdata
413              
414             Alias for reports_dpath_search.
415              
416             =head1 UTILITY FUNCTIONS
417              
418             =head2 cache_single_dpath
419              
420             Cache a result for a raw dpath on a report id.
421              
422             =head2 cached_single_dpath
423              
424             Return cached result for a raw dpath on a report id.
425              
426             =head2 cache_whole_dpath
427              
428             Cache a result for a complete tapper::dpath on all reports.
429              
430             =head2 cached_whole_dpath
431              
432             Return cached result for a complete tapper::dpath on all reports.
433              
434             =head1 AUTHORS
435              
436             =over 4
437              
438             =item *
439              
440             AMD OSRC Tapper Team <tapper@amd64.org>
441              
442             =item *
443              
444             Tapper Team <tapper-ops@amazon.com>
445              
446             =back
447              
448             =head1 COPYRIGHT AND LICENSE
449              
450             This software is Copyright (c) 2016 by Advanced Micro Devices, Inc..
451              
452             This is free software, licensed under:
453              
454             The (two-clause) FreeBSD License
455              
456             =cut