File Coverage

blib/lib/Sport/Analytics/NHL/Test.pm
Criterion Covered Total %
statement 508 546 93.0
branch 176 224 78.5
condition 194 289 67.1
subroutine 82 82 100.0
pod 57 57 100.0
total 1017 1198 84.8


line stmt bran cond sub pod time code
1             package Sport::Analytics::NHL::Test;
2              
3 39     39   18657 use v5.10.1;
  39         123  
4 39     39   201 use strict;
  39         80  
  39         827  
5 39     39   171 use warnings FATAL => 'all';
  39         80  
  39         1335  
6 39     39   277 use experimental qw(smartmatch);
  39         73  
  39         380  
7              
8 39     39   2190 use parent 'Exporter';
  39         87  
  39         233  
9              
10 39     39   1767 use Carp;
  39         76  
  39         1989  
11 39     39   3907 use Data::Dumper;
  39         41368  
  39         1701  
12 39     39   6837 use Storable;
  39         32419  
  39         1965  
13              
14 39     39   4507 use List::MoreUtils qw(uniq);
  39         88464  
  39         360  
15              
16 39     39   24457 use Sport::Analytics::NHL::Config;
  39         175  
  39         6375  
17 39     39   260 use Sport::Analytics::NHL::LocalConfig;
  39         64  
  39         4808  
18 39     39   3145 use Sport::Analytics::NHL::Util;
  39         87  
  39         2321  
19 39     39   3673 use Sport::Analytics::NHL::Tools;
  39         86  
  39         5886  
20 39     39   7753 use Sport::Analytics::NHL::Errors;
  39         174  
  39         22707  
21              
22             =head1 NAME
23              
24             Sport::Analytics::NHL::Test - Utilities to test NHL reports data.
25              
26             =head1 SYNOPSYS
27              
28             Utilities to test NHL report data
29              
30             These are utilities that test and validate the data contained in the NHL reports to detect errors. They are also used to test and validate the permutations that are performed by this software on the data.
31             Ideally, that method should extend Test::More, but first, I was too lazy to figure out how to do it, and second, I notice that in the huge number of tests that are run, Test::More begins to drag things down.
32              
33             use Sport::Analytics::NHL::Test;
34             test_team_id('SJS') # pass
35             test_team_id('S.J') # fail and die (usually)
36              
37             The failures are usually bad enough to force the death of the program and an update to Sport::Analytics::NHL::Errors (q.v.), but see the next section
38              
39             =head1 GLOBAL VARIABLES
40              
41             The behaviour of the tests is controlled by several global variables:
42             * $TEST_COUNTER - contains the number of the current test in Curr_Test field and the number of passes/fails in Test_Results.
43             * $DO_NOT_DIE - when set to 1, failed test will not die.
44             * $MESSAGE - the latest failure message
45             * $TEST_ERRORS - accumulation of errors by type (event, player, boxscore, team)
46              
47             =head1 FUNCTIONS
48              
49             =over 2
50              
51             =item C
52              
53             Either dies with a stack trace dump, or aggregates the error messages, based on $DO_NOT_DIE
54             Arguments: the death message
55             Returns: void
56              
57             =item C
58              
59             Executes a test subroutine and sets the failure message in case of failure. Updates test counters.
60             Arguments: the test subroutine and its arguments
61             Returns: void
62              
63             =item C
64              
65             Approximately the same as Test::More::like()
66              
67             =item C
68              
69             Approximately the same as Test::More::is()
70              
71             =item C
72              
73             Approximately the same as Test::More::ok()
74              
75             =item C
76              
77             Approximately the same as grep {$_[0] == $_} $_[1]
78              
79             =item C
80              
81             For the test_* functions below the second argument is always the notification message. Sometimes third parameter may be passed. This one tests if the season is one between $FIRST_SEASON (from Sports::Analytics::NHL::Config) and $CURRENT_SEASON (from Sports::Analytics::NHL::LocalConfig)
82              
83             =item C
84              
85             Tests if the stage is either Regular (2) or Playoff (3)
86              
87             =item C
88              
89             Tests the season Id to be between 1 and 1500 (supposedly maximum number of games per reg. season)
90              
91             =item C
92              
93             Tests the game Id to be of the SSSSTIIII form. In case optional parameter is_nhl, tests for the NHL id SSSSTTIIII
94              
95             =item C
96              
97             Tests if the string is a three-letter team code, not necessarily the normalized one.
98              
99             =item C
100              
101             Tests if the string is a three-letter franchise code, as specified in keys of Sports::Analytics::NHL::Config::TEAMS
102              
103             =item C
104              
105             Tests the timestamp to be an integer (negative for pre-1970 games) number.
106              
107             =item C
108              
109             Tests the game date to be in YYYYMMDD format.
110              
111             =item C
112              
113             Check if the particular stat is measured in season being processed, stored in $THIS_SEASON
114              
115             =item C
116              
117             Set the stats tested for a player
118              
119             =item C
120              
121             Tests the correct values in assists and servedby fields
122              
123             =item C
124              
125             Overall sequence to test the entire boxscore
126              
127             =item C
128              
129             Tests the event coordinates
130              
131             =item C
132              
133             Tests the decision for the goaltender being one of W,L,O,T
134              
135             =item C
136              
137             Overall sequence to test the event
138              
139             =item C
140              
141             Route the particular event testing according to its type
142              
143             =item C
144              
145             Checks the applicability of coordinates for the event and tests them
146              
147             =item C
148              
149             Tests the event's description (for existence, actually)
150              
151             =item C
152              
153             Checks the applicability of strength setting for the event and tests it
154              
155             =item C
156              
157             Test the boxscore's events (loops over test_event (q.v.))
158              
159             =item C
160              
161             Tests the goal event
162              
163             =item C
164              
165             Tests the header information of the boxscore
166              
167             =item C
168              
169             Tests the player's name (to have a space between two words, more or less)
170              
171             =item C
172              
173             Tests the officials definition in the boxscore
174              
175             =item C
176              
177             Tests the penalty event
178              
179             =item C
180              
181             Tests the periods' reports from the boxscore
182              
183             =item C
184              
185             Tests a player entry in the team's roster
186              
187             =item C
188              
189             Tests valid population of the player1 event field
190              
191             =item C
192              
193             Tests valid population of the player2 event field
194              
195             =item C
196              
197             Tests the player id to be the valid 7-digit one one starting with 8
198              
199             =item C
200              
201             Tests the position of the player to be one of C L R F D G
202              
203             =item C
204              
205             Tests the event's strength to be one of EV, SH, PP, PS or XX (unknown)
206              
207             =item C
208              
209             Tests "header" information for a team: shots, score, coach etc.
210              
211             =item C
212              
213             Tests teams that played in the game
214              
215             =item C
216              
217             Tests the time to be of format M{1,3}:SS
218              
219             =item C
220              
221             Tests the boxscore after it was merged with other files (e.g. via Sport::Analytics::NHL::Merger). Test options are set according to the types of reports that have been merged ($boxscore->{sources})
222              
223             =item C
224              
225             Tests the events of the merged boxscore
226              
227             =item C
228              
229             Tests the header of the merged boxscore
230              
231             =item C
232              
233             Tests the teams of the merged boxscore
234              
235             =item C
236              
237             Approximately the same as Test::More::cmp_ok()
238              
239             =item C
240              
241             Tests that the normalized events have been arranged correctly
242              
243             =item C
244              
245             Tests the consistency between the event summary and the boxscore
246              
247             =item C
248              
249             Tests the consistency between the goaltender data and the event summary
250              
251             =item C
252              
253             Tests the consistency between the penalty minutes of the players in the boxscore and in the event summary
254              
255             =item C
256              
257             Tests the consistency between the goals scored in the events and the goals in the boxscore teams/players
258              
259             =item C
260              
261             Tests the consistency between the skater data and the event summary
262              
263             =item C
264              
265             Full test of the normalized boxscore
266              
267             =item C
268              
269             Tests the normalized boxscore events
270              
271             =item C
272              
273             Tests the normalized boxscore's header
274              
275             =item C
276              
277             Tests the normalized roster of a team
278              
279             =item C
280              
281             Tests the normalized boxscore's teams
282              
283             =back
284              
285             =cut
286              
287             our $TEST_COUNTER = {Curr_Test => 0, Test_Results => []};
288              
289             our @EXPORT = qw(
290             my_like my_ok my_is
291             test_game_id test_team_id test_team_code
292             test_stage test_season test_season_id
293             test_ts test_game_date
294             test_header test_periods test_officials test_teams test_events
295             test_boxscore test_merged_boxscore
296             test_consistency test_normalized_boxscore
297             $TEST_COUNTER
298             $EVENT $BOXSCORE $PLAYER $TEAM
299             );
300              
301             our $DO_NOT_DIE = 0;
302             our $TEST_ERRORS = {};
303             our $MESSAGE = '';
304             our $THIS_SEASON;
305              
306             our $EVENT;
307             our $BOXSCORE;
308             our $PLAYER;
309              
310             $Data::Dumper::Trailingcomma = 1;
311             $Data::Dumper::Deepcopy = 1;
312             $Data::Dumper::Sortkeys = 1;
313             $Data::Dumper::Deparse = 1;
314              
315             sub my_die ($) {
316              
317 68     68 1 91 my $message = shift;
318 68 50       115 if ($DO_NOT_DIE) {
319 68         98 my $field;
320             my $object;
321 68 50       122 if ($EVENT) {
    50          
322 0         0 $field = 'events';
323 0         0 $object = $EVENT;
324             }
325             elsif ($PLAYER) {
326 0         0 $field = 'players';
327 0         0 $object = $PLAYER;
328             }
329             else {
330 68         84 $field = 'boxscore';
331 68         87 $object = $BOXSCORE;
332             }
333 68   50     274 $TEST_ERRORS->{$field} ||= [];
334             push(
335 68         362 @{$TEST_ERRORS->{$field}},
336             {
337             _id => $object->{_id} || $object->{event_idx} || $object->{number},
338 68   33     79 message => $MESSAGE,
339             }
340             );
341 68         131 return;
342             }
343 0 0       0 $message .= "\n" unless $message =~ /\n$/;
344 0         0 my $c = 0;
345 0         0 my $offset = '';
346 0         0 while (my @caller = caller($c++)) {
347 0         0 $message .= sprintf(
348             "%sCalled in %s::%s, line %d in %s\n",
349             $offset, $caller[0], $caller[3], $caller[2], $caller[1]
350             );
351 0         0 $offset .= ' ';
352             }
353 0         0 die $message;
354             }
355              
356             sub my_test ($@) {
357              
358 224983     224983 1 275042 my $test = shift;
359 224983         265091 $TEST_COUNTER->{Curr_Test}++;
360 39     39   284 no warnings 'uninitialized';
  39         70  
  39         7115  
361 224983 100       323671 if (@_ == 2) {
362 149937         230265 $MESSAGE = "Failed $_[-1]: $_[0]";
363             }
364             else {
365 75046 100 100     200501 if (ref $_[1] && ref $_[1] eq 'ARRAY') {
366 74         90 my $arg1 = join('/', @{$_[1]});
  74         161  
367 74         160 $MESSAGE = "Failed $_[-1]: $_[0] vs $arg1\n";
368             }
369             else {
370 74972         164575 $MESSAGE = "Failed $_[-1]: $_[0] vs $_[1]\n";
371             }
372             }
373 224983 100       315559 if ($test->(@_)) {
374 224915         287316 $TEST_COUNTER->{Test_Results}[0]++;
375             }
376             else {
377 68         114 $TEST_COUNTER->{Test_Results}[1]++;
378 68         104 my_die($MESSAGE);
379             }
380 39     39   246 use warnings FATAL => 'all';
  39         79  
  39         4541  
381 224983 50       859927 debug "ok_$TEST_COUNTER->{Curr_Test} - $_[-1]" if $0 =~ /\.t$/;
382             }
383              
384 39     39 1 271 sub my_like ($$$) { my_test(sub { no warnings 'uninitialized'; $_[0] =~ $_[1] }, @_) }
  39     72021   95  
  39     72021   3194  
  72021         293333  
  72021         186987  
385 39     39 1 228 sub my_is ($$$) { my_test(sub { no warnings 'uninitialized'; $_[0] eq $_[1] }, @_) }
  39     2951   81  
  39     2951   3062  
  2951         5710  
  2951         8456  
386 39     39 1 250 sub my_ok ($$) { my_test(sub { no warnings 'uninitialized'; $_[0] }, @_) }
  39     149937   87  
  39     149937   2629  
  149937         232812  
  149937         350488  
387 39     39 1 226 sub my_is_one_of ($$$) { my_test(sub { no warnings 'uninitialized'; grep { $_[0] == $_ } @{$_[1]}}, @_) }
  39     74   80  
  39     74   284358  
  74         79  
  222         386  
  74         112  
  74         209  
388              
389             sub my_cmp_ok ($$$$) {
390 1539     1539 1 3863 my ($got, $type, $expect, $message) = @_;
391 1539         1805 my $test;
392 1539         61571 eval qq{
393             \$test = (\$got $type \$expect);
394             1;
395             };
396 1539 50       4197 my_die($@) if $@;
397 1539         2818 my_ok($test, $message);
398             }
399              
400              
401             sub test_season ($$) {
402 23519     23519 1 46791 my $season = shift;
403 23519         27053 my $message = shift;
404 23519         50267 my_ok($season >= $FIRST_SEASON, $message); my_ok($season <= $CURRENT_SEASON, $message);
  23519         61676  
405 23519         47111 $THIS_SEASON = $season;
406             }
407              
408             sub test_stage ($$) {
409 23519     23519 1 46373 my $stage = shift;
410 23519         27959 my $message = shift;
411 23519         44245 my_ok($stage >= $REGULAR, 'stage ok'); my_ok($stage <= $PLAYOFF, $message);
  23519         48686  
412             }
413              
414             sub test_season_id ($$) {
415 23519     23519 1 42461 my $id = shift;
416 23519         27130 my $message = shift;
417 23519         46475 my_ok($id > 0, $message); my_ok($id < 1500, $message);
  23519         48565  
418             }
419              
420             sub test_game_id ($$;$) {
421 17702     17702 1 1351719 my $id = shift;
422 17702         21869 my $message = shift;
423 17702   100     33920 my $is_nhl = shift || 0;
424              
425 17702 100       52227 $is_nhl
426             ? $id =~ /^(\d{4})(\d{2})(\d{4})$/
427             : $id =~ /^(\d{4})(\d{1})(\d{4})$/;
428 17702         34871 test_season($1, $message);
429 17702         34271 test_stage($2, $message);
430 17702         38358 test_season_id($3, $message);
431             }
432              
433             sub test_team_code ($$) {
434 17854     17854 1 70806 my_like(shift, qr/^\w{3}$/, shift .' tri letter code a team');
435             }
436              
437 12430 50   12430 1 53230 sub test_team_id ($$) { test_team_code($_[0],$_[1]) && my_ok($TEAMS{$_[0]}, "$_[0] team defined")};
438 5807     5807 1 31130 sub test_ts ($$) { my_like(shift, qr/^-?\d+$/, shift) }
439 5807     5807 1 28043 sub test_game_date ($$) { my_like(shift, qr/^\d{8}$/, shift) }
440              
441             sub is_unapplicable ($) {
442 6534     6534 1 7964 my $data = shift;
443              
444             $THIS_SEASON < (
445             $DATA_BY_SEASON{$data} || $STAT_RECORD_FROM{$data} || $data
446 6534 100 66     37038 ) || $EVENT && $EVENT->{time} eq '00:00' && $EVENT->{period} < 2;
      33        
      66        
447             };
448              
449             sub test_header ($) {
450              
451 10     10 1 43 my $bs = shift;
452              
453 10         60 test_season( $bs->{season}, 'header season ok');
454 10         65 test_stage( $bs->{stage}, 'header stage ok');
455 10         90 test_season_id($bs->{season_id}, 'header season id ok');
456 10         90 test_game_id( $bs->{_id}, 'header game id ok');
457              
458 10         79 my_is($bs->{status}, 'FINAL', 'only final games');
459 10 100       101 my_ok($bs->{location}, 'location set') unless is_unapplicable('location');
460              
461             my_like($bs->{ot}, qr/^0|1$/, 'OT detected')
462 10 50       31 if @{$bs->{periods}} > 3;
  10         75  
463             my_like($bs->{so}, qr/^0|1$/, 'SO detected')
464 10 50 33     27 if @{$bs->{periods}} > 4 && $bs->{stage} == $REGULAR;
  10         66  
465 10 50 33     51 if ($bs->{so} && ref $bs->{shootout}) {
466 0         0 for my $team (qw(away home)) {
467 0         0 for my $stat (qw(attempts scores)) {
468 0         0 my_like($bs->{shootout}{$team}{$stat}, qr/^\d+$/, 'shootout stat ok');
469             }
470             }
471             }
472             }
473              
474             sub test_officials ($;$) {
475              
476 4     4 1 12 my $officials = shift;
477 4         9 return 1; # for now
478              
479 0         0 for my $o (qw(referees linesmen)) {
480 0         0 for my $of (@{$officials->{$o}}) {
  0         0  
481 0         0 my_ok($of->{name}, 'name set');
482             }
483             }
484             }
485              
486 429     429 1 1206 sub test_name ($$) { my_like(shift, qr/\w|\.\s+\w/, shift.' first and last name') ; }
487 18715     18715 1 68822 sub test_player_id ($$) { my_like(shift, qr/^8\d{6}$/, shift.' valid player id') ; }
488 2712     2712 1 7351 sub test_time ($$) { my_like(shift, qr/^\-?\d{1,3}:\d{1,2}$/, shift.' valid time') ; }
489 410     410 1 1247 sub test_position ($$) { my_like(shift, qr/^(C|R|W|F|D|L|G)$/, shift.' valid pos defined') ; }
490 12     12 1 56 sub test_decision ($$) { my_like(shift, qr/^W|L|O|T|N$/, shift.' valid decision') ; }
491 555     555 1 1839 sub test_strength ($$) { my_like(shift, qr/^EV|SH|PP|PS|XX$/, shift.' valid strength') ; }
492              
493             sub test_periods ($) {
494              
495 5     5 1 15 my $periods = shift;
496              
497 5         24 for my $p (0..4) {
498 25         41 my $period = $periods->[$p];
499 25 100 66     84 next if ! $period && $p > 2;
500 15         47 my_is($period->{id}, $p+1, 'period id ok');
501 15         114 my_like($period->{type}, qr/^REGULAR|OVERTIME$/, 'period time ok');
502 15         45 my_is(scalar(@{$period->{score}}), 4, '4 items in score');
  15         46  
503 15         46 for my $gssg (@{$period->{score}}) {
  15         42  
504 60         150 my_like($gssg, qr/^\d+$/, 'gssg in period a number');
505             }
506             }
507             }
508              
509             sub test_coords ($) {
510              
511 1490     1490 1 2416 my $coords = shift;
512              
513 1490 100       1750 return if scalar keys %{$coords} < 2;
  1490         4577  
514 1235         1566 my_is(scalar(keys %{$coords}), 2, '2 coords');
  1235         2953  
515              
516 1235         2756 for my $coord (keys %{$coords}) {
  1235         3095  
517 2470         7140 my_like($coord, qr/^x|y$/, 'coord x or y');
518 2470         7944 my_like($coords->{$coord}, qr/^\-?\d+$/, 'event coord ok');
519             }
520             }
521              
522             sub test_team_header ($;$) {
523              
524 18     18 1 33 my $team = shift;
525 18   50     44 my $opts = shift || {};
526              
527             test_team_code($team->{name}, 'team name ok')
528 18 100 100     176 unless $opts->{es} || $opts->{gs} || $opts->{ro};
      100        
529             test_name( $team->{coach}, 'team coach ok')
530 18 100 100     138 unless $opts->{es} || $opts->{gs};
531 18 100       85 my_like($team->{shots}, qr/^\d{1,2}$/, 'shots a number') if $opts->{bs};
532 18         86 my_like($team->{score}, qr/^1?\d$/, 'goals < 20');
533 18 100       176 my_like($team->{pull}, qr/^1|0$/, 'goalie either pulled or not') if $opts->{bs};
534 18         74 for my $scratch (@{$team->{scratches}}) {
  18         56  
535             $opts->{ro} ?
536 25 100       64 test_name($scratch->{name}, 'scratch name ok in ro') :
537             test_player_id($scratch, 'scratch id ok');
538             }
539             }
540              
541             sub set_tested_stats ($$) {
542              
543 304     304 1 361 my $player = shift;
544 304   50     453 my $opts = shift || {};
545              
546 304         374 my @stats;
547 304 50       563 return () if $player->{missing};
548 304 100       616 if ($opts->{gs}) {
    100          
    100          
549             @stats = $player->{old} ?
550 4 50       13 qw(timeOnIce shots saves goals) :
551             qw(timeOnIce number powerPlayTimeOnIce shortHandedTimeOnIce evenTimeOnIce shots saves goals);
552             }
553             elsif ($opts->{ro}) {
554 40         70 @stats = qw(number start);
555             }
556             elsif ($opts->{es}) {
557              
558             }
559             else {
560 220 100       854 @stats = $player->{position} eq 'G' ?
561             qw(pim evenShotsAgainst shots timeOnIce shortHandedShotsAgainst assists shortHandedSaves powerPlayShotsAgainst powerPlaySaves evenSaves number saves goals) :
562             qw(penaltyMinutes shortHandedAssists goals evenTimeOnIce takeaways blocked assists hits powerPlayTimeOnIce plusMinus powerPlayGoals giveaways faceoffTaken faceOffWins shortHandedGoals powerPlayAssists number timeOnIce shots shortHandedTimeOnIce);
563 220 100       393 $stats[0] = 'penaltyMinutes' if $opts->{merged};
564             }
565 304         909 @stats;
566             }
567              
568             sub test_player ($;$) {
569              
570 304     304 1 377 my $player = shift;
571 304   50     495 my $opts = shift || {};
572              
573 304         517 my @stats = set_tested_stats($player, $opts);
574 304         653 test_position($player->{position}, 'roster position ok');
575 304         863 for my $stat (@stats) {
576             next if is_unapplicable($STAT_RECORD_FROM{$stat})
577 4428 100 66     7424 || $player->{position} eq 'G' && $opts->{es};
      66        
578 3992 50       7156 if (! defined $player->{$stat}) {print Dumper $stat, $player;exit;}
  0         0  
  0         0  
579             $stat =~ /timeonice/i ?
580             $opts->{es} || $opts->{gs} ?
581             my_like($player->{$stat}, qr/^\d{1,5}$/, "ES $stat ok") :
582             test_time($player->{$stat}, "$stat timeonice ok") :
583 3992 100 66     14400 my_like($player->{$stat}, qr/\-?\d{1,2}/, "stat $stat an integer");
    100          
584             }
585 304         710 test_name($player->{name}, 'player name ok');
586             test_player_id($player->{_id}, 'roster id ok')
587 304 100 100     1540 unless $opts->{es} || $opts->{gs} || $opts->{ro};
      100        
588              
589             }
590              
591             sub test_teams ($;$) {
592              
593 9     9 1 27 my $teams = shift;
594 9   100     37 my $opts = shift || {};
595              
596 9         23 for my $team (@{$teams}) {
  9         28  
597 18         72 test_team_header($team, $opts);
598 18         118 my $decision = '';
599 18         35 my $broken = 0;
600 18         35 for my $player (@{$team->{roster}}) {
  18         48  
601 324 50 66     1039 next if $player->{_id} && $player->{_id} =~ /^80/;
602 324         406 $PLAYER = $player;
603 324 100       554 if ($player->{broken}) {
604 20         38 $broken = 1;
605 20         37 next;
606             }
607 304         566 test_player($player, $opts);
608 304 100       818 if (! $decision) {
    50          
609 207         331 $decision = $player->{decision};
610             }
611             elsif ($player->{decision}) {
612 0         0 die "Cannot have two decisions";
613             }
614 304         493 undef $PLAYER;
615             }
616             test_decision($decision, 'game decision ok')
617             unless $broken
618             || $BOXSCORE->{_gs_no_g}
619             || $opts->{es}
620 18 100 66     191 || $opts->{ro};
      66        
      100        
621 18 100       77 $team->{decision} = $decision if $opts->{merged};
622             }
623 9         32 undef $PLAYER;
624             }
625              
626             sub test_event_strength ($$$) {
627              
628 1952     1952 1 2558 my $event = shift;
629 1952         2176 my $opts = shift;
630 1952         2331 my $message = shift;
631              
632             test_strength($event->{strength}, $message)
633             if $event->{type} eq 'GOAL' || $opts->{merged} && (
634             !$BROKEN_TIMES{$BOXSCORE->{_id}}
635             && $event->{type} ne 'CHL'
636             && !($event->{type} eq 'PENL' && ! $event->{sources}{PL})
637             && ($event->{type} eq 'GOAL' || $BOXSCORE->{sources}{PL}
638             && ! is_noplay_event($event))
639             && !($event->{type} eq 'MISS' && ! $event->{sources}{PL})
640 1952 100 33     10786 );
      100        
      66        
      66        
      100        
      66        
      66        
      100        
      100        
641             }
642              
643             sub test_event_coords ($) {
644 1952     1952 1 2331 my $event = shift;
645              
646             test_coords($event->{coordinates})
647             if !is_unapplicable('coordinates')
648             && !is_noplay_event($event)
649             && !($event->{penalty})
650 1952 100 66     3110 && !($BROKEN_COORDS{$BOXSCORE->{_id}});
651             }
652              
653             sub test_event_description ($) {
654 1952     1952 1 2433 my $event = shift;
655              
656             my_like($event->{description}, qr/\w/, 'event description exists')
657             if $BOXSCORE->{sources}{BS}
658             && !$BROKEN_FILES{$BOXSCORE->{_id}}->{BS}
659 1952 100 66     7987 || $BOXSCORE->{sources}{PL};
      100        
660             }
661              
662             sub test_assists_and_servedby ($$) {
663 1952     1952 1 2429 my $event = shift;
664 1952   50     3333 my $opts = shift || {};
665              
666 1952 100       3746 if ($event->{servedby}) {
667             $opts->{pl} ?
668             my_like($event->{player1}, qr/^(\d{1,2}|80\d{5})$/, 'pl player1 number ok') :
669 7 100       74 test_player_id($event->{servedby}, 'servedby player id ok');
670             }
671 1952 100 100     4275 if ($event->{assists} && @{$event->{assists}}) {
  34         145  
672 33         49 for my $assist (@{$event->{assists}}) {
  33         81  
673             $opts->{pl} ?
674 66 100       153 my_like($event->{player1}, qr/^(\d{1,2}|80\d{5})$/, 'pl assist number ok') :
675             test_player_id($assist, 'assist id ok');
676             }
677             }
678             }
679              
680             sub test_player1 ($$) {
681 1619     1619 1 2050 my $event = shift;
682 1619         1978 my $opts = shift;
683              
684 1619 100 66     5660 if (($opts->{gs} && ! $event->{old}) || $opts->{pl}) {
      100        
685 268         994 my_like($event->{player1}, qr/^(\d{1,2}|80\d{5})$/, 'gs pl player1 number ok');
686             }
687             else {
688 1351         1656 $DO_NOT_DIE = 1;
689             test_player_id($event->{player1}, 'event player1 ok')
690             unless $opts->{gs}
691             || ($event->{type} eq 'PENL'
692             && ($event->{time} eq '20:00'
693             || $PENALTY_POSSIBLE_NO_OFFENDER{$event->{penalty}})
694 1351 100 66     5674 );
      100        
      66        
695 1351         3502 $DO_NOT_DIE = 0;
696             }
697             }
698              
699             sub test_player2 ($$) {
700 807     807 1 1087 my $event = shift;
701 807         940 my $opts = shift;
702              
703             test_player_id($event->{player2}, 'event player2 ok')
704             unless ($event->{type} eq 'GOAL' && $event->{en})
705             || ($event->{type} eq 'GOAL' && $opts->{bh} || $opts->{gs} || $opts->{pl})
706             || ($opts->{merged} && ! $event->{sources}{BS} && $event->{type} eq 'GOAL')
707 807 50 66     8024 || ($event->{time} eq '0:00' && $event->{type} ne 'FAC');
      100        
      100        
      100        
      66        
      66        
      33        
      66        
      66        
      33        
708             }
709              
710             sub test_goal ($$) {
711 39     39 1 65 my $event = shift;
712 39         56 my $opts = shift;
713              
714 39 50 66     300 unless (
      66        
      33        
      33        
715             $opts->{pb} || $opts->{pl} || $event->{so}
716             || $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} && $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} == $NO_EVENTS
717             ) {
718 34 100 66     163 my_like($event->{en}, qr/^0|1$/, 'en definition') if $event->{sources}{BS} || $event->{sources}{GS};
719             my_like($event->{gwg}, qr/^0|1$/, 'gwg definition')
720 34 100       113 if $opts->{bs};
721             }
722             }
723              
724             sub test_penalty ($$) {
725 128     128 1 164 my $event = shift;
726 128         294 my $opts = shift;
727 128 50       265 unless ($opts->{pb}) {
728             my_like(
729             $event->{severity},
730             qr/^major|misconduct|minor|game|match|double|shot$/i, 'severity defined'
731             ) unless ! defined $event->{severity} || is_unapplicable('severity')
732             || $opts->{bh}
733             || $opts->{gs}
734             || $opts->{pl}
735             || !$event->{length}
736 128 50 100     399 || $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} && $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} == $NO_EVENTS;
      66        
      33        
      33        
      33        
      33        
      33        
737 128         596 my_ok($VOCABULARY{penalty}->{$event->{penalty}}, "$event->{penalty} Good penalty type");
738 128         448 my_like($event->{length}, qr/^0|2|3|4|5|10$/, 'length defined');
739             }
740             }
741              
742             sub test_event_by_type ($$) {
743 1952     1952 1 2391 my $event = shift;
744 1952         2260 my $opts = shift;
745              
746 1952         4961 my_ok($VOCABULARY{events}->{$event->{type}}, "$event->{type} Good event type");
747             my_ok($VOCABULARY{strength}->{$event->{strength}}, 'Good event strength')
748 1952 50       16345 if exists $event->{strength};
749 1952         4614 for ($event->{type}) {
750 1952         6778 when ([ qw(FAC HIT BLOCK GOAL SHOT PENL MISS GIVE TAKE) ]) {
751 1619         3445 test_player1($event, $opts);
752 1619         2734 continue;
753             }
754 1952         5427 when ([ qw(FAC HIT BLOCK GOAL) ]) {
755 807         2023 test_player2($event, $opts);
756 807         1832 continue;
757             }
758 1952         3427 when ('STOP') {
759 294         972 my_is(ref $event->{stopreason}, 'ARRAY', 'stopreason is array');
760 294         598 for my $reason (@{$event->{stopreason}}) {
  294         685  
761             my_ok(
762 322         905 $VOCABULARY{stopreason}->{$reason},
763             "$reason there is a good reason to stop",
764             );
765             }
766 294         475 continue;
767             }
768 1952         3710 when ([ qw(GOAL SHOT) ]) {
769             my_ok(
770             $VOCABULARY{shot_type}->{$event->{shot_type}},
771 417         1402 "$event->{shot_type} shot type normalized",
772             );
773 417         808 continue;
774             }
775 1952         3500 when ([ qw(GOAL) ]) {
776 39         119 test_goal($event, $opts);
777 39         68 continue;
778             }
779 1952         3240 when ([ qw(MISS) ]) {
780             my_ok(
781             $VOCABULARY{miss}->{$event->{miss}},
782 192         540 'miss type normalized',
783             );
784             my_like($event->{description}, qr/\w/, 'miss needs description')
785 192 50       872 unless $event->{penaltyshot};
786 192         482 continue;
787             }
788 1952         4246 when ([ qw(PENL) ]) {
789 128         309 test_penalty($event, $opts);
790 128         408 continue;
791             }
792             }
793              
794             }
795              
796             sub test_event ($;$) {
797              
798 1952     1952 1 2831 my $event = shift;
799 1952   50     3336 my $opts = shift || {};
800              
801 1952         2216 $EVENT = $event;
802 1952         8787 my_like($event->{period}, qr/^\d$/, 'event period ok');
803 1952         7714 test_time($event->{time}, 'event time ok');
804 1952         8818 test_event_strength($event, $opts, "event $event->{type}/$event->{time}");
805 1952         4656 test_event_coords($event);
806 1952         4619 test_event_description($event);
807 1952         5638 my_ok($VOCABULARY{events}->{$event->{type}}, 'valid type');
808 1952         5618 test_assists_and_servedby($event, $opts);
809 1952         3807 test_event_by_type($event, $opts);
810 1952         3908 undef $EVENT;
811             }
812              
813             sub test_events ($;$) {
814              
815 9     9 1 33 my $events = shift;
816 9   100     33 my $opts = shift || {};
817              
818 9         21 my $event_n = scalar @{$events};
  9         23  
819              
820             my_ok($event_n >= $REASONABLE_EVENTS{
821             $BOXSCORE->{season} < 2010 ? 'old' : 'new'
822             }, " $BOXSCORE->{_id} enough events($event_n) read")
823             unless
824             $ZERO_EVENT_GAMES{$BOXSCORE->{_id}} ||
825             ($BROKEN_FILES{$BOXSCORE->{_id}}{BS} && $BROKEN_FILES{$BOXSCORE->{_id}}{BS} == $NO_EVENTS) &&
826             (!$BOXSCORE->{sources}{GS} && !$BOXSCORE->{sources}{PL})
827 9 100 33     225 || $opts->{bh} || $opts->{gs};
    100 0        
      33        
      33        
      66        
      100        
828 9         35 for my $event (@{$events}) {
  9         32  
829 1952         2989 test_event($event, $opts);
830             }
831 9         60 undef $EVENT;
832             }
833              
834             sub test_boxscore ($;$) {
835              
836 6     6 1 75 my $boxscore = shift;
837 6   50     29 my $opts = shift || {bs => 0};
838              
839 6         18 $BOXSCORE = $boxscore;
840 6         35 test_header($boxscore);
841 6 100       32 test_periods($boxscore->{periods}) if $opts->{bs};
842             test_officials($boxscore->{officials}, $opts)
843 6 100 100     69 if ! $opts->{es} && ! $opts->{pl} && $boxscore->{season} >= $DATA_BY_SEASON{officials};
      100        
844 6 100       59 test_teams($boxscore->{teams}, $opts) if ! $opts->{pl};
845             test_events($boxscore->{events}, $opts) unless
846 6 100 33     103 $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} && $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} == $NO_EVENTS || $opts->{es} || $opts->{ro};
      66        
      100        
847 6         17 undef $BOXSCORE;
848 6         15 undef $PLAYER;
849 6         18 undef $EVENT;
850             }
851              
852             sub test_merged_header ($) {
853              
854 3     3 1 9 my $bs = shift;
855 3         24 test_header($bs);
856              
857 3 100 66     316 my_like($bs->{attendance}, qr/^\d+$/, 'attendance set')
858             if $BOXSCORE->has_html() || ! is_unapplicable('attendance');
859 3 50       25 my_like($bs->{tz}, qr/^\w{1,2}T$/, 'tz ok') if $bs->has_html();
860 3         22 my_like($bs->{month}, qr/^(0|1)?\d?/, 'month ok');
861             }
862              
863             sub test_merged_teams ($) {
864              
865 3     3 1 8 my $teams = shift;
866 3         16 my $opts = {merged => 1};
867 3         19 test_teams($teams, $opts);
868             }
869              
870             sub test_merged_events ($) {
871              
872 4     4 1 25 my $events = shift;
873 4         16 my $opts = {merged => 1};
874              
875 4         21 test_events($events, $opts);
876             }
877              
878             sub test_merged_boxscore ($) {
879              
880 3     3 1 11126 my $boxscore = shift;
881 3         2267 $BOXSCORE = $boxscore;
882 3         43 test_merged_header($boxscore);
883 3         34 test_merged_teams($boxscore->{teams});
884 3         16 test_periods($boxscore->{periods});
885 3         14 test_merged_events($boxscore->{events});
886 3         9 undef $BOXSCORE;
887 3         6 undef $EVENT;
888 3         14 undef $PLAYER;
889             }
890              
891             sub test_consistency_penalty_minutes ($$) {
892              
893 38     38 1 47 my $roster_player = shift;
894 38         39 my $event_player = shift;
895              
896 38   100     99 $event_player->{penaltyMinutes} ||= 0;
897 38   100     114 $event_player->{servedbyMinutes} ||= 0;
898             my_is_one_of(
899             $roster_player->{penaltyMinutes},
900             [
901             $event_player->{penaltyMinutes},
902             $event_player->{penaltyMinutes} + $event_player->{servedbyMinutes},
903             $event_player->{penaltyMinutes} - $event_player->{servedbyMinutes},
904             ],
905             "Player $roster_player->{_id}/$roster_player->{name} penaltyMinutes consistent"
906 38 50 33     186 ) if defined $roster_player->{penaltyMinutes} && $roster_player->{penaltyMinutes} != -1;
907 38 100       115 if ($roster_player->{penaltyMinutes} == $event_player->{penaltyMinutes} - $event_player->{servedbyMinutes}) {
908 37         52 $roster_player->{penaltyMinutes} += $event_player->{servedbyMinutes};
909             }
910             }
911              
912             sub test_consistency_goalie ($$$) {
913              
914 2     2 1 3 my $roster_player = shift;
915 2         4 my $event_player = shift;
916 2         2 my $boxscore_id = shift;
917              
918             my_is(
919             $roster_player->{shots} - $roster_player->{saves},
920             $event_player->{goalsAgainst} || 0,
921             "Player $roster_player->{_id}/$roster_player->{name} goalsAgainst consistent"
922 2 50 50     7 ) unless $BROKEN_FILES{$boxscore_id}->{BS} || is_unapplicable('saves');
      33        
923             }
924              
925             sub test_consistency_skater ($$$$) {
926              
927 36     36 1 46 my $roster_player = shift;
928 36         36 my $event_player = shift;
929 36         39 my $boxscore_id = shift;
930 36         40 my $stats = shift;
931              
932 36         68 for my $stat (@{$stats}) {
  36         52  
933 108 100       170 next if $stat eq 'penaltyMinutes';
934 72 100 66     184 if ($stat eq 'goals' || $stat eq 'assists') {
935             my_is(
936             $roster_player->{$stat},
937 36   100     140 $event_player->{$stat} || 0,
938             "Player $roster_player->{_id}/$roster_player->{name} $stat consistent"
939             );
940 36         92 return;
941             }
942 36 50       62 next unless defined $roster_player->{$stat};
943             my_is_one_of(
944             $roster_player->{$stat},
945             [
946             $event_player->{$stat} - 1,
947             $event_player->{$stat},
948             $event_player->{$stat} + 1,
949             ],
950             "Player $roster_player->{_id}/$roster_player->{name} $stat consistent"
951 36 50 33     79 ) unless $BROKEN_FILES{BS}->{$boxscore_id} || is_unapplicable($stat);
952             }
953             }
954              
955             sub test_consistency_playergoals ($$) {
956              
957 1     1 1 2 my $boxscore = shift;
958 1         1 my $event_summary = shift;
959              
960 1         3 for my $t (0, 1) {
961 2         4 my $team = $boxscore->{teams}[$t];
962 2         3 for my $player (@{$team->{roster}}) {
  2         4  
963 43   100     112 $player->{goals} ||= 0;
964 43 100       55 if ($player->{position} eq 'G') {
965             $event_summary->{$team->{name}}{playergoals} +=
966 2   50     18 ($event_summary->{$player->{_id}}{g_goals} || 0);
967             }
968             else {
969 41         60 $event_summary->{$team->{name}}{playergoals} += $player->{goals};
970             }
971             }
972             my_is(
973             $team->{score},
974 2         13 $event_summary->{$team->{name}}{playergoals} + $event_summary->{so}[$t],
975             "Team $team->{name} ($t) playergoals consistent",
976             );
977             }
978             }
979              
980             sub test_consistency ($$) {
981              
982 1     1 1 3 my $boxscore = shift;
983 1         1 my $event_summary = shift;
984              
985 1         5 $THIS_SEASON = $boxscore->{season};
986 1         9 $BOXSCORE = $boxscore;
987 1         3 for my $t (0,1) {
988 2         5 my $team = $boxscore->{teams}[$t];
989             my_is(
990             ($event_summary->{$team->{name}}{score} || 0),
991             $team->{score},
992             "Team $team->{name} score $team->{score} consistent"
993 2 50 50     24 ) unless $BROKEN_FILES{$boxscore->{_id}}->{BS};
994 2         6 for my $player (@{$team->{roster}}) {
  2         6  
995 43 100 66     120 next if $player->{broken} || $player->{position} eq 'N/A';
996 38         77 $PLAYER = $player;
997 38         80 test_consistency_penalty_minutes($player, $event_summary->{$player->{_id}});
998             $player->{position} eq 'G' ?
999             test_consistency_goalie($player, $event_summary->{$player->{_id}}, $boxscore->{_id}) :
1000 38 100       98 test_consistency_skater($player, $event_summary->{$player->{_id}}, $boxscore->{_id}, $event_summary->{stats});
1001             }
1002 2         5 undef $PLAYER;
1003             }
1004             test_consistency_playergoals($boxscore, $event_summary)
1005 1 50       6 unless $BROKEN_FILES{$boxscore->{_id}}->{BS};
1006             }
1007              
1008             sub test_normalized_header ($) {
1009              
1010 3     3 1 7 my $boxscore = shift;
1011              
1012 3 50       26 if ($boxscore->{teams}[0]{score} > $boxscore->{teams}[1]{score}) {
    50          
1013 0         0 my_is($boxscore->{result}[0], 2, 'winner correct in result');
1014 0 0 0     0 my_is($boxscore->{result}[1], $boxscore->{season} > 1998 && $boxscore->{ot} ? 1 : 0, 'loser correct in result');
1015             }
1016             elsif ($boxscore->{teams}[0]{score} < $boxscore->{teams}[1]{score}) {
1017 3         21 my_is($boxscore->{result}[1], 2, 'winner correct in result');
1018 3 50 66     52 my_is($boxscore->{result}[0], $boxscore->{season} > 1998 && $boxscore->{ot} ? 1 : 0, 'loser correct in result');
1019             }
1020             else {
1021 0         0 my_is($boxscore->{result}[0], 1, 'tie correct in result');
1022 0         0 my_is($boxscore->{result}[1], 1, 'tie correct in result');
1023             }
1024 3         54 my_like($boxscore->{date}, qr/^\d{8}$/, 'game date set correctly');
1025 3 100       60 my_ok($boxscore->{location}, 'location set') unless is_unapplicable('location');
1026 3         21 my $path = get_game_path_from_id($boxscore->{_id});
1027 3         18 for my $source (qw(BS PL RO GS ES)) {
1028             my_is($boxscore->{sources}{$source}, 1 , "source $source registered")
1029 15 100 66     316 if $source eq 'BS' || (-f "$path/$source.html" && ! $BROKEN_FILES{$boxscore->{_id}}{$source});
      100        
1030             }
1031 3         25 for my $field (qw(_id attendance last_updated month date ot start_ts stop_ts stage season season_id)) {
1032 33         135 my_like($boxscore->{$field}, qr/^\-?\d+$/, "$field a number");
1033             }
1034             }
1035              
1036             sub test_normalized_roster ($$) {
1037              
1038 6     6 1 10 my $roster = shift;
1039 6         13 my $team_name = shift;
1040              
1041 6         7 for my $player (@{$roster}) {
  6         17  
1042 106         148 for (keys %{$player}) {
  106         729  
1043 3022         4397 my $field = $_;
1044 3022         4555 when ('position') { test_position($player->{$_}, 'position ok') }
  106         214  
1045 2916         3507 when ('name') { test_name($player->{$_}, 'name ok') };
  106         211  
1046 2810         3331 when ('status') {
1047 76         210 my_like($player->{$field}, qr/^(C|A| |X)$/, 'status ok');
1048             }
1049 2734         3155 when ('start') {
1050 76         214 my_like($player->{$field}, qr/^(0|1|2)$/, 'start ok');
1051             }
1052 2658         3062 when ('plusMinus') {
1053 72         204 my_like($player->{$field}, qr/^\-?\d+$/, '+- ok');
1054             }
1055 2586         3014 when ('decision') {
1056 6 50       23 if ($player->{position} eq 'G') {
1057 6         29 test_decision($player->{$field}, 'decision ok');
1058             }
1059             else {
1060 0         0 my_die("skater $player->{_id} should not have decision");
1061             }
1062             }
1063 2580         2901 when ('team') {
1064 106         222 my_is($player->{team}, $team_name, 'team in player ok');
1065             }
1066 2474         2762 default {
1067             my_like(
1068             $player->{$field},
1069             qr/[+-]?([0-9]*[.])?[0-9]+/, "stat $field a number"
1070 2474 50       8493 ) if defined $player->{$field};
1071             }
1072             }
1073             }
1074             }
1075              
1076             sub test_normalized_teams ($) {
1077              
1078 3     3 1 8 my $boxscore = shift;
1079 3         14 for my $t (0,1) {
1080 6         19 my $team = $boxscore->{teams}[$t];
1081 6         12 for my $stat (keys %{$team->{stats}}) {
  6         37  
1082 50         173 my_like($team->{stats}{$stat}, qr/[+-]?([0-9]*[.])?[0-9]+/, "team $stat a number");
1083             }
1084 6         22 for my $field (qw(pull shots score)) {
1085 18         63 my_like($team->{$field}, qr/[+-]?([0-9]*[.])?[0-9]+/, "team $field a number");
1086             }
1087 6         35 my_ok(! exists $team->{_decision}, 'pseudo-decision removed');
1088 6         35 test_normalized_roster($team->{roster}, $team->{name});
1089             }
1090             }
1091              
1092             sub test_normalized_events ($) {
1093              
1094 3     3 1 8 my $boxscore = shift;
1095              
1096             return if $BROKEN_FILES{$boxscore->{_id}}->{BS} &&
1097 3 50 33     23 $BROKEN_FILES{$boxscore->{_id}}->{BS} == $UNSYNCHED;
1098 3         9 for my $event (@{$boxscore->{events}}) {
  3         18  
1099 666         2831 test_game_id($event->{game_id}, 'event has game');
1100 666 100       1904 my_like($event->{zone}, qr/^(OFF|DEF|NEU|UNK)$/, 'event has zone')
1101             unless is_noplay_event($event);
1102 666 100       1910 my_is(length($event->{strength}), 2, 'event has strength')
1103             unless is_noplay_event($event);
1104 666         1573 for my $field (qw(period season stage so ts)) {
1105             my_like($event->{$field}, qr/^\d+$/, "field $field a number")
1106 3330 100       12348 if defined $event->{$field};
1107             }
1108             test_event_coords($event)
1109 666 50       1343 if $event->{coords};
1110 666 100       1275 my_like($event->{t}, qr/^(-1|0|1)$/, 'event t index ok')
1111             unless is_noplay_event($event);
1112             my_like($event->{en}, qr/^(0|1)$/, 'event en ok')
1113 666 100       1960 if exists $event->{en};
1114             my_is(
1115             $event->{team2},
1116             $boxscore->{teams}[1-$event->{t}]{name}, 'team2 ok'
1117 666 100 100     3229 ) if defined $event->{t} && $event->{t} != -1;
1118 666         1550 for my $field (qw(player1 player2 assist1 assist2)) {
1119             test_player_id($event->{$field}, "field $field ok")
1120 2664 100       6118 if exists $event->{$field};
1121             }
1122 666 100       1440 if ($event->{on_ice}) {
1123 624         892 for my $t (0,1) {
1124 1248         1547 for my $o (@{$event->{on_ice}[$t]}) {
  1248         4016  
1125 7208         10960 test_player_id($o, 'valid player id on ice');
1126             }
1127             }
1128             }
1129 666         1193 for ($event->{type}) {
1130 666         1678 when ('GOAL') {
1131 11         56 test_player_id($event->{player1}, "goal scorer player1 ok");
1132             test_player_id($event->{player2}, "goal goalie player2 ok")
1133 11 50       91 unless $event->{en};
1134 11         69 for my $field (qw(en gwg penaltyshot)) {
1135 33         132 my_like($event->{$field}, qr/^0|1$/, "goal $field ok")
1136             }
1137 11 100       60 if ($event->{assist1}) {
1138 10         30 test_player_id($event->{assist1}, 'assist1 ok');
1139 10         71 my_is($event->{assist1}, $event->{assists}[0], 'in array');
1140 10 50       49 if ($event->{assist2}) {
1141 10         40 test_player_id($event->{assist2}, 'assist2 ok');
1142 10         56 my_is($event->{assist2}, $event->{assists}[1], 'in array');
1143             }
1144             }
1145 11         53 when ('PENL') {
1146             my_ok($event->{ps_penalty}, 'ps penalty')
1147 0 0       0 if $event->{length} == 0;
1148 0         0 test_penalty($event->{penalty}, 'penalty defined');
1149             test_player_id($event->{servedby}, 'servedby ok')
1150 0 0       0 if $event->{servedby};
1151             }
1152 11         26 when ('FAC') {
1153 0         0 test_team($event->{winning_team}, 'FAC winning team ok');
1154             }
1155 11 50       33 if ($event->{type} ne 'GOAL') {
1156 0         0 my_ok(!defined $event->{assist1}, 'no goal no assist1');
1157 0         0 my_ok(!defined $event->{assist2}, 'no goal no assist2');
1158 0         0 my_ok(!defined $event->{assists}, 'no goal no assists');
1159             }
1160             my_ok(
1161             $VOCABULARY{shot_type}->{$event->{shot_type}},
1162 11         66 "$event->{shot_type} shot type normalized",
1163             );
1164 11         32 my @fields = keys %{$event};
  11         144  
1165 11         36 for my $field (@fields) {
1166 325         782 my_ok(defined $field, "existing field $field defined");
1167 325 100 66     1169 next if $field eq 'file' || ref $event->{$field};
1168 279 100       687 if ($event->{$field} =~ /\D/) {
1169 88         221 my_is($event->{$field}, uc($event->{$field}), 'all UC ok');
1170             }
1171             else {
1172 191         470 my_like($event->{$field}, qr/^\d+$/, 'numeric field ok');
1173             }
1174             }
1175             }
1176             }
1177             }
1178             }
1179              
1180             sub test_arranged_events ($) {
1181              
1182 3     3 1 9 my $boxscore = shift;
1183              
1184 3         5 my $gp = scalar @{$boxscore->{periods}};
  3         9  
1185 3         17 my_is($boxscore->{events}[-1]{type}, 'GEND', 'gend at the end');
1186 3         26 my_is($boxscore->{events}[-2]{type}, 'PEND', 'pend penultimate');
1187 3         10 my_is(scalar(grep{$_->{type} eq 'PSTR'} @{$boxscore->{events}}), $gp, "$gp pstr");
  666         1368  
  3         29  
1188 3         21 my_is(scalar(grep{$_->{type} eq 'PEND'} @{$boxscore->{events}}), $gp, "$gp pend");
  666         945  
  3         25  
1189 3         26 my_is(scalar(grep{$_->{type} eq 'GEND'} @{$boxscore->{events}}), 1, '1 gend');
  666         918  
  3         21  
1190              
1191 3         16 for my $e (0..$#{$boxscore->{events}}-1) {
  3         40  
1192             my_cmp_ok(
1193             $boxscore->{events}[$e]{period},
1194             '<=',
1195             $boxscore->{events}[$e+1]{period},
1196 663         2830 'period ordered'
1197             );
1198             my_cmp_ok(
1199             $boxscore->{events}[$e]{ts},
1200             '<=',
1201             $boxscore->{events}[$e+1]{ts},
1202             'ts ordered'
1203             ) if $boxscore->{events}[$e]{period} ==
1204 663 100       3920 $boxscore->{events}[$e+1]{period};
1205             my_cmp_ok(
1206             $Sport::Analytics::NHL::Normalizer::EVENT_PRECEDENCE{
1207             $boxscore->{events}[$e]{type}
1208             },
1209             '<=',
1210             $Sport::Analytics::NHL::Normalizer::EVENT_PRECEDENCE{
1211             $boxscore->{events}[$e+1]{type}
1212             },
1213             'precedence ordered'
1214             ) if
1215             $boxscore->{events}[$e]{period} ==
1216             $boxscore->{events}[$e+1]{period}
1217             && $boxscore->{events}[$e]{ts} ==
1218 663 100 100     4233 $boxscore->{events}[$e+1]{ts};
1219 663         1196 my $event = $boxscore->{events}[$e];
1220 663         3718 my_like($event->{_id}, qr/^$boxscore->{_id}\d{4}$/, '_id created');
1221 663 100       3204 if ($event->{type} eq 'PSTR') {
    100          
    50          
1222 9         44 my_like($event->{ts}, qr/^(0|\d{2,3}00)$/, 'period starts at 00');
1223 9         53 my_like($event->{time}, qr/^\d+:00$/, 'period starts at :00');
1224             }
1225             elsif ($event->{type} eq 'PEND') {
1226 9         42 my_ok($event->{ts}, 'pend timestamp defined');
1227             }
1228             elsif ($event->{type} eq 'GEND') {
1229 0         0 my_die "Should not get to GEND";
1230             }
1231             }
1232             }
1233              
1234             sub test_normalized_boxscore ($) {
1235              
1236 3     3 1 9783 my $boxscore = shift;
1237              
1238 3         13 $THIS_SEASON = $boxscore->{season};
1239 3         25 test_normalized_header($boxscore);
1240 3         22 test_normalized_teams($boxscore);
1241 3         35 test_normalized_events($boxscore);
1242 3         28 test_arranged_events($boxscore);
1243             }
1244              
1245             END {
1246 39 100   39   363521 if ($BOXSCORE) {
1247 1         6 $Data::Dumper::Varname = 'BOXSCORE';
1248             }
1249 39 50       341 if ($EVENT) {
1250 0         0 $Data::Dumper::Varname = 'EVENT';
1251 0         0 print Dumper $EVENT;
1252             }
1253 39 50       1785 if ($PLAYER) {
1254 0         0 $Data::Dumper::Varname = 'PLAYER';
1255 0         0 print Dumper $PLAYER;
1256             }
1257             }
1258              
1259             1;
1260              
1261             =head1 AUTHOR
1262              
1263             More Hockey Stats, C<< >>
1264              
1265             =head1 BUGS
1266              
1267             Please report any bugs or feature requests to C, or through
1268             the web interface at L. I will be notified, and then you'll
1269             automatically be notified of progress on your bug as I make changes.
1270              
1271              
1272             =head1 SUPPORT
1273              
1274             You can find documentation for this module with the perldoc command.
1275              
1276             perldoc Sport::Analytics::NHL::Test
1277              
1278             You can also look for information at:
1279              
1280             =over 4
1281              
1282             =item * RT: CPAN's request tracker (report bugs here)
1283              
1284             L
1285              
1286             =item * AnnoCPAN: Annotated CPAN documentation
1287              
1288             L
1289              
1290             =item * CPAN Ratings
1291              
1292             L
1293              
1294             =item * Search CPAN
1295              
1296             L
1297              
1298             =back