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   15735 use v5.10.1;
  39         127  
4 39     39   163 use strict;
  39         60  
  39         719  
5 39     39   150 use warnings FATAL => 'all';
  39         60  
  39         1172  
6 39     39   237 use experimental qw(smartmatch);
  39         67  
  39         247  
7              
8 39     39   1994 use parent 'Exporter';
  39         63  
  39         187  
9              
10 39     39   1574 use Carp;
  39         70  
  39         1733  
11 39     39   3302 use Data::Dumper;
  39         34575  
  39         1529  
12 39     39   5942 use Storable;
  39         27677  
  39         1709  
13              
14 39     39   3626 use List::MoreUtils qw(uniq);
  39         74434  
  39         354  
15              
16 39     39   22807 use Sport::Analytics::NHL::Config;
  39         156  
  39         5807  
17 39     39   225 use Sport::Analytics::NHL::LocalConfig;
  39         56  
  39         4025  
18 39     39   2565 use Sport::Analytics::NHL::Util;
  39         80  
  39         2135  
19 39     39   3168 use Sport::Analytics::NHL::Tools;
  39         75  
  39         5343  
20 39     39   6586 use Sport::Analytics::NHL::Errors;
  39         134  
  39         20849  
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 81 my $message = shift;
318 68 50       108 if ($DO_NOT_DIE) {
319 68         77 my $field;
320             my $object;
321 68 50       106 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         71 $field = 'boxscore';
331 68         79 $object = $BOXSCORE;
332             }
333 68   50     288 $TEST_ERRORS->{$field} ||= [];
334             push(
335 68         355 @{$TEST_ERRORS->{$field}},
336             {
337             _id => $object->{_id} || $object->{event_idx} || $object->{number},
338 68   33     86 message => $MESSAGE,
339             }
340             );
341 68         126 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 260140 my $test = shift;
359 224983         248034 $TEST_COUNTER->{Curr_Test}++;
360 39     39   246 no warnings 'uninitialized';
  39         64  
  39         6564  
361 224983 100       304622 if (@_ == 2) {
362 149937         219735 $MESSAGE = "Failed $_[-1]: $_[0]";
363             }
364             else {
365 75046 100 100     186382 if (ref $_[1] && ref $_[1] eq 'ARRAY') {
366 74         91 my $arg1 = join('/', @{$_[1]});
  74         153  
367 74         153 $MESSAGE = "Failed $_[-1]: $_[0] vs $arg1\n";
368             }
369             else {
370 74972         149034 $MESSAGE = "Failed $_[-1]: $_[0] vs $_[1]\n";
371             }
372             }
373 224983 100       299761 if ($test->(@_)) {
374 224915         269070 $TEST_COUNTER->{Test_Results}[0]++;
375             }
376             else {
377 68         81 $TEST_COUNTER->{Test_Results}[1]++;
378 68         101 my_die($MESSAGE);
379             }
380 39     39   223 use warnings FATAL => 'all';
  39         72  
  39         4093  
381 224983 50       810749 debug "ok_$TEST_COUNTER->{Curr_Test} - $_[-1]" if $0 =~ /\.t$/;
382             }
383              
384 39     39 1 248 sub my_like ($$$) { my_test(sub { no warnings 'uninitialized'; $_[0] =~ $_[1] }, @_) }
  39     72021   57  
  39     72021   2963  
  72021         268582  
  72021         171759  
385 39     39 1 232 sub my_is ($$$) { my_test(sub { no warnings 'uninitialized'; $_[0] eq $_[1] }, @_) }
  39     2951   68  
  39     2951   2798  
  2951         5100  
  2951         7355  
386 39     39 1 210 sub my_ok ($$) { my_test(sub { no warnings 'uninitialized'; $_[0] }, @_) }
  39     149937   80  
  39     149937   2412  
  149937         215836  
  149937         328048  
387 39     39 1 208 sub my_is_one_of ($$$) { my_test(sub { no warnings 'uninitialized'; grep { $_[0] == $_ } @{$_[1]}}, @_) }
  39     74   75  
  39     74   256770  
  74         76  
  222         374  
  74         111  
  74         210  
388              
389             sub my_cmp_ok ($$$$) {
390 1539     1539 1 3054 my ($got, $type, $expect, $message) = @_;
391 1539         1602 my $test;
392 1539         54024 eval qq{
393             \$test = (\$got $type \$expect);
394             1;
395             };
396 1539 50       3840 my_die($@) if $@;
397 1539         2549 my_ok($test, $message);
398             }
399              
400              
401             sub test_season ($$) {
402 23519     23519 1 44857 my $season = shift;
403 23519         25585 my $message = shift;
404 23519         47925 my_ok($season >= $FIRST_SEASON, $message); my_ok($season <= $CURRENT_SEASON, $message);
  23519         57976  
405 23519         44976 $THIS_SEASON = $season;
406             }
407              
408             sub test_stage ($$) {
409 23519     23519 1 43851 my $stage = shift;
410 23519         25400 my $message = shift;
411 23519         42673 my_ok($stage >= $REGULAR, 'stage ok'); my_ok($stage <= $PLAYOFF, $message);
  23519         46060  
412             }
413              
414             sub test_season_id ($$) {
415 23519     23519 1 40401 my $id = shift;
416 23519         26068 my $message = shift;
417 23519         43924 my_ok($id > 0, $message); my_ok($id < 1500, $message);
  23519         45808  
418             }
419              
420             sub test_game_id ($$;$) {
421 17702     17702 1 1205540 my $id = shift;
422 17702         20645 my $message = shift;
423 17702   100     32742 my $is_nhl = shift || 0;
424              
425 17702 100       49297 $is_nhl
426             ? $id =~ /^(\d{4})(\d{2})(\d{4})$/
427             : $id =~ /^(\d{4})(\d{1})(\d{4})$/;
428 17702         33053 test_season($1, $message);
429 17702         31648 test_stage($2, $message);
430 17702         34905 test_season_id($3, $message);
431             }
432              
433             sub test_team_code ($$) {
434 17854     17854 1 64579 my_like(shift, qr/^\w{3}$/, shift .' tri letter code a team');
435             }
436              
437 12430 50   12430 1 47742 sub test_team_id ($$) { test_team_code($_[0],$_[1]) && my_ok($TEAMS{$_[0]}, "$_[0] team defined")};
438 5807     5807 1 29854 sub test_ts ($$) { my_like(shift, qr/^-?\d+$/, shift) }
439 5807     5807 1 26091 sub test_game_date ($$) { my_like(shift, qr/^\d{8}$/, shift) }
440              
441             sub is_unapplicable ($) {
442 6534     6534 1 7051 my $data = shift;
443              
444             $THIS_SEASON < (
445             $DATA_BY_SEASON{$data} || $STAT_RECORD_FROM{$data} || $data
446 6534 100 66     31298 ) || $EVENT && $EVENT->{time} eq '00:00' && $EVENT->{period} < 2;
      33        
      66        
447             };
448              
449             sub test_header ($) {
450              
451 10     10 1 33 my $bs = shift;
452              
453 10         50 test_season( $bs->{season}, 'header season ok');
454 10         45 test_stage( $bs->{stage}, 'header stage ok');
455 10         45 test_season_id($bs->{season_id}, 'header season id ok');
456 10         53 test_game_id( $bs->{_id}, 'header game id ok');
457              
458 10         50 my_is($bs->{status}, 'FINAL', 'only final games');
459 10 100       49 my_ok($bs->{location}, 'location set') unless is_unapplicable('location');
460              
461             my_like($bs->{ot}, qr/^0|1$/, 'OT detected')
462 10 50       25 if @{$bs->{periods}} > 3;
  10         42  
463             my_like($bs->{so}, qr/^0|1$/, 'SO detected')
464 10 50 33     17 if @{$bs->{periods}} > 4 && $bs->{stage} == $REGULAR;
  10         51  
465 10 50 33     58 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 10 my $officials = shift;
477 4         6 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 1045 sub test_name ($$) { my_like(shift, qr/\w|\.\s+\w/, shift.' first and last name') ; }
487 18715     18715 1 68315 sub test_player_id ($$) { my_like(shift, qr/^8\d{6}$/, shift.' valid player id') ; }
488 2712     2712 1 6376 sub test_time ($$) { my_like(shift, qr/^\-?\d{1,3}:\d{1,2}$/, shift.' valid time') ; }
489 410     410 1 1018 sub test_position ($$) { my_like(shift, qr/^(C|R|W|F|D|L|G)$/, shift.' valid pos defined') ; }
490 12     12 1 49 sub test_decision ($$) { my_like(shift, qr/^W|L|O|T|N$/, shift.' valid decision') ; }
491 555     555 1 1446 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 12 my $periods = shift;
496              
497 5         24 for my $p (0..4) {
498 25         37 my $period = $periods->[$p];
499 25 100 66     74 next if ! $period && $p > 2;
500 15         44 my_is($period->{id}, $p+1, 'period id ok');
501 15         66 my_like($period->{type}, qr/^REGULAR|OVERTIME$/, 'period time ok');
502 15         34 my_is(scalar(@{$period->{score}}), 4, '4 items in score');
  15         41  
503 15         26 for my $gssg (@{$period->{score}}) {
  15         32  
504 60         120 my_like($gssg, qr/^\d+$/, 'gssg in period a number');
505             }
506             }
507             }
508              
509             sub test_coords ($) {
510              
511 1490     1490 1 1976 my $coords = shift;
512              
513 1490 100       1650 return if scalar keys %{$coords} < 2;
  1490         3639  
514 1235         1397 my_is(scalar(keys %{$coords}), 2, '2 coords');
  1235         2641  
515              
516 1235         2411 for my $coord (keys %{$coords}) {
  1235         2589  
517 2470         6056 my_like($coord, qr/^x|y$/, 'coord x or y');
518 2470         6813 my_like($coords->{$coord}, qr/^\-?\d+$/, 'event coord ok');
519             }
520             }
521              
522             sub test_team_header ($;$) {
523              
524 18     18 1 26 my $team = shift;
525 18   50     38 my $opts = shift || {};
526              
527             test_team_code($team->{name}, 'team name ok')
528 18 100 100     198 unless $opts->{es} || $opts->{gs} || $opts->{ro};
      100        
529             test_name( $team->{coach}, 'team coach ok')
530 18 100 100     115 unless $opts->{es} || $opts->{gs};
531 18 100       63 my_like($team->{shots}, qr/^\d{1,2}$/, 'shots a number') if $opts->{bs};
532 18         72 my_like($team->{score}, qr/^1?\d$/, 'goals < 20');
533 18 100       134 my_like($team->{pull}, qr/^1|0$/, 'goalie either pulled or not') if $opts->{bs};
534 18         67 for my $scratch (@{$team->{scratches}}) {
  18         46  
535             $opts->{ro} ?
536 25 100       63 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 317 my $player = shift;
544 304   50     396 my $opts = shift || {};
545              
546 304         315 my @stats;
547 304 50       452 return () if $player->{missing};
548 304 100       552 if ($opts->{gs}) {
    100          
    100          
549             @stats = $player->{old} ?
550 4 50       12 qw(timeOnIce shots saves goals) :
551             qw(timeOnIce number powerPlayTimeOnIce shortHandedTimeOnIce evenTimeOnIce shots saves goals);
552             }
553             elsif ($opts->{ro}) {
554 40         62 @stats = qw(number start);
555             }
556             elsif ($opts->{es}) {
557              
558             }
559             else {
560 220 100       744 @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       316 $stats[0] = 'penaltyMinutes' if $opts->{merged};
564             }
565 304         796 @stats;
566             }
567              
568             sub test_player ($;$) {
569              
570 304     304 1 318 my $player = shift;
571 304   50     443 my $opts = shift || {};
572              
573 304         440 my @stats = set_tested_stats($player, $opts);
574 304         543 test_position($player->{position}, 'roster position ok');
575 304         742 for my $stat (@stats) {
576             next if is_unapplicable($STAT_RECORD_FROM{$stat})
577 4428 100 66     6384 || $player->{position} eq 'G' && $opts->{es};
      66        
578 3992 50       6158 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     12255 my_like($player->{$stat}, qr/\-?\d{1,2}/, "stat $stat an integer");
    100          
584             }
585 304         579 test_name($player->{name}, 'player name ok');
586             test_player_id($player->{_id}, 'roster id ok')
587 304 100 100     1285 unless $opts->{es} || $opts->{gs} || $opts->{ro};
      100        
588              
589             }
590              
591             sub test_teams ($;$) {
592              
593 9     9 1 40 my $teams = shift;
594 9   100     32 my $opts = shift || {};
595              
596 9         19 for my $team (@{$teams}) {
  9         25  
597 18         58 test_team_header($team, $opts);
598 18         82 my $decision = '';
599 18         28 my $broken = 0;
600 18         27 for my $player (@{$team->{roster}}) {
  18         35  
601 324 50 66     841 next if $player->{_id} && $player->{_id} =~ /^80/;
602 324         370 $PLAYER = $player;
603 324 100       463 if ($player->{broken}) {
604 20         27 $broken = 1;
605 20         33 next;
606             }
607 304         490 test_player($player, $opts);
608 304 100       655 if (! $decision) {
    50          
609 228         281 $decision = $player->{decision};
610             }
611             elsif ($player->{decision}) {
612 0         0 die "Cannot have two decisions";
613             }
614 304         422 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     121 || $opts->{ro};
      66        
      100        
621 18 100       71 $team->{decision} = $decision if $opts->{merged};
622             }
623 9         29 undef $PLAYER;
624             }
625              
626             sub test_event_strength ($$$) {
627              
628 1952     1952 1 2268 my $event = shift;
629 1952         1997 my $opts = shift;
630 1952         2023 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     9436 );
      100        
      66        
      66        
      100        
      66        
      66        
      100        
      100        
641             }
642              
643             sub test_event_coords ($) {
644 1952     1952 1 2031 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     2536 && !($BROKEN_COORDS{$BOXSCORE->{_id}});
651             }
652              
653             sub test_event_description ($) {
654 1952     1952 1 2157 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     6732 || $BOXSCORE->{sources}{PL};
      100        
660             }
661              
662             sub test_assists_and_servedby ($$) {
663 1952     1952 1 2156 my $event = shift;
664 1952   50     2888 my $opts = shift || {};
665              
666 1952 100       3082 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       47 test_player_id($event->{servedby}, 'servedby player id ok');
670             }
671 1952 100 100     3307 if ($event->{assists} && @{$event->{assists}}) {
  34         100  
672 33         51 for my $assist (@{$event->{assists}}) {
  33         69  
673             $opts->{pl} ?
674 66 100       141 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 1801 my $event = shift;
682 1619         1702 my $opts = shift;
683              
684 1619 100 66     4866 if (($opts->{gs} && ! $event->{old}) || $opts->{pl}) {
      100        
685 268         743 my_like($event->{player1}, qr/^(\d{1,2}|80\d{5})$/, 'gs pl player1 number ok');
686             }
687             else {
688 1351         1571 $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     4677 );
      100        
      66        
695 1351         3064 $DO_NOT_DIE = 0;
696             }
697             }
698              
699             sub test_player2 ($$) {
700 807     807 1 915 my $event = shift;
701 807         837 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     6401 || ($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 57 my $event = shift;
712 39         49 my $opts = shift;
713              
714 39 50 66     283 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     161 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       101 if $opts->{bs};
721             }
722             }
723              
724             sub test_penalty ($$) {
725 128     128 1 162 my $event = shift;
726 128         251 my $opts = shift;
727 128 50       229 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     346 || $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} && $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} == $NO_EVENTS;
      66        
      33        
      33        
      33        
      33        
      33        
737 128         477 my_ok($VOCABULARY{penalty}->{$event->{penalty}}, "$event->{penalty} Good penalty type");
738 128         378 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 2160 my $event = shift;
744 1952         2075 my $opts = shift;
745              
746 1952         4634 my_ok($VOCABULARY{events}->{$event->{type}}, "$event->{type} Good event type");
747             my_ok($VOCABULARY{strength}->{$event->{strength}}, 'Good event strength')
748 1952 50       14699 if exists $event->{strength};
749 1952         3972 for ($event->{type}) {
750 1952         5940 when ([ qw(FAC HIT BLOCK GOAL SHOT PENL MISS GIVE TAKE) ]) {
751 1619         2946 test_player1($event, $opts);
752 1619         2325 continue;
753             }
754 1952         4755 when ([ qw(FAC HIT BLOCK GOAL) ]) {
755 807         1542 test_player2($event, $opts);
756 807         1594 continue;
757             }
758 1952         3030 when ('STOP') {
759 294         706 my_is(ref $event->{stopreason}, 'ARRAY', 'stopreason is array');
760 294         518 for my $reason (@{$event->{stopreason}}) {
  294         556  
761             my_ok(
762 322         699 $VOCABULARY{stopreason}->{$reason},
763             "$reason there is a good reason to stop",
764             );
765             }
766 294         389 continue;
767             }
768 1952         3229 when ([ qw(GOAL SHOT) ]) {
769             my_ok(
770             $VOCABULARY{shot_type}->{$event->{shot_type}},
771 417         1487 "$event->{shot_type} shot type normalized",
772             );
773 417         746 continue;
774             }
775 1952         3244 when ([ qw(GOAL) ]) {
776 39         96 test_goal($event, $opts);
777 39         71 continue;
778             }
779 1952         2782 when ([ qw(MISS) ]) {
780             my_ok(
781             $VOCABULARY{miss}->{$event->{miss}},
782 192         463 'miss type normalized',
783             );
784             my_like($event->{description}, qr/\w/, 'miss needs description')
785 192 50       770 unless $event->{penaltyshot};
786 192         406 continue;
787             }
788 1952         3833 when ([ qw(PENL) ]) {
789 128         251 test_penalty($event, $opts);
790 128         339 continue;
791             }
792             }
793              
794             }
795              
796             sub test_event ($;$) {
797              
798 1952     1952 1 2339 my $event = shift;
799 1952   50     2963 my $opts = shift || {};
800              
801 1952         2050 $EVENT = $event;
802 1952         6005 my_like($event->{period}, qr/^\d$/, 'event period ok');
803 1952         5894 test_time($event->{time}, 'event time ok');
804 1952         7250 test_event_strength($event, $opts, "event $event->{type}/$event->{time}");
805 1952         4034 test_event_coords($event);
806 1952         4086 test_event_description($event);
807 1952         4885 my_ok($VOCABULARY{events}->{$event->{type}}, 'valid type');
808 1952         5008 test_assists_and_servedby($event, $opts);
809 1952         3448 test_event_by_type($event, $opts);
810 1952         3314 undef $EVENT;
811             }
812              
813             sub test_events ($;$) {
814              
815 9     9 1 26 my $events = shift;
816 9   100     30 my $opts = shift || {};
817              
818 9         22 my $event_n = scalar @{$events};
  9         21  
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     233 || $opts->{bh} || $opts->{gs};
    100 0        
      33        
      33        
      66        
      100        
828 9         26 for my $event (@{$events}) {
  9         28  
829 1952         2656 test_event($event, $opts);
830             }
831 9         42 undef $EVENT;
832             }
833              
834             sub test_boxscore ($;$) {
835              
836 6     6 1 63 my $boxscore = shift;
837 6   50     41 my $opts = shift || {bs => 0};
838              
839 6         17 $BOXSCORE = $boxscore;
840 6         28 test_header($boxscore);
841 6 100       30 test_periods($boxscore->{periods}) if $opts->{bs};
842             test_officials($boxscore->{officials}, $opts)
843 6 100 100     58 if ! $opts->{es} && ! $opts->{pl} && $boxscore->{season} >= $DATA_BY_SEASON{officials};
      100        
844 6 100       41 test_teams($boxscore->{teams}, $opts) if ! $opts->{pl};
845             test_events($boxscore->{events}, $opts) unless
846 6 100 33     77 $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} && $BROKEN_FILES{BS}->{$BOXSCORE->{_id}} == $NO_EVENTS || $opts->{es} || $opts->{ro};
      66        
      100        
847 6         13 undef $BOXSCORE;
848 6         10 undef $PLAYER;
849 6         19 undef $EVENT;
850             }
851              
852             sub test_merged_header ($) {
853              
854 3     3 1 5 my $bs = shift;
855 3         20 test_header($bs);
856              
857 3 100 66     256 my_like($bs->{attendance}, qr/^\d+$/, 'attendance set')
858             if $BOXSCORE->has_html() || ! is_unapplicable('attendance');
859 3 50       14 my_like($bs->{tz}, qr/^\w{1,2}T$/, 'tz ok') if $bs->has_html();
860 3         17 my_like($bs->{month}, qr/^(0|1)?\d?/, 'month ok');
861             }
862              
863             sub test_merged_teams ($) {
864              
865 3     3 1 7 my $teams = shift;
866 3         17 my $opts = {merged => 1};
867 3         24 test_teams($teams, $opts);
868             }
869              
870             sub test_merged_events ($) {
871              
872 4     4 1 22 my $events = shift;
873 4         13 my $opts = {merged => 1};
874              
875 4         22 test_events($events, $opts);
876             }
877              
878             sub test_merged_boxscore ($) {
879              
880 3     3 1 9468 my $boxscore = shift;
881 3         1915 $BOXSCORE = $boxscore;
882 3         30 test_merged_header($boxscore);
883 3         16 test_merged_teams($boxscore->{teams});
884 3         16 test_periods($boxscore->{periods});
885 3         12 test_merged_events($boxscore->{events});
886 3         8 undef $BOXSCORE;
887 3         7 undef $EVENT;
888 3         11 undef $PLAYER;
889             }
890              
891             sub test_consistency_penalty_minutes ($$) {
892              
893 38     38 1 45 my $roster_player = shift;
894 38         40 my $event_player = shift;
895              
896 38   100     99 $event_player->{penaltyMinutes} ||= 0;
897 38   100     106 $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     182 ) if defined $roster_player->{penaltyMinutes} && $roster_player->{penaltyMinutes} != -1;
907 38 100       121 if ($roster_player->{penaltyMinutes} == $event_player->{penaltyMinutes} - $event_player->{servedbyMinutes}) {
908 37         58 $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         5 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 47 my $roster_player = shift;
928 36         42 my $event_player = shift;
929 36         45 my $boxscore_id = shift;
930 36         44 my $stats = shift;
931              
932 36         39 for my $stat (@{$stats}) {
  36         60  
933 108 100       166 next if $stat eq 'penaltyMinutes';
934 72 100 66     175 if ($stat eq 'goals' || $stat eq 'assists') {
935             my_is(
936             $roster_player->{$stat},
937 36   100     149 $event_player->{$stat} || 0,
938             "Player $roster_player->{_id}/$roster_player->{name} $stat consistent"
939             );
940 36         83 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     73 ) unless $BROKEN_FILES{BS}->{$boxscore_id} || is_unapplicable($stat);
952             }
953             }
954              
955             sub test_consistency_playergoals ($$) {
956              
957 1     1 1 3 my $boxscore = shift;
958 1         2 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     152 $player->{goals} ||= 0;
964 43 100       63 if ($player->{position} eq 'G') {
965             $event_summary->{$team->{name}}{playergoals} +=
966 2   50     8 ($event_summary->{$player->{_id}}{g_goals} || 0);
967             }
968             else {
969 41         58 $event_summary->{$team->{name}}{playergoals} += $player->{goals};
970             }
971             }
972             my_is(
973             $team->{score},
974 2         11 $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         4 $THIS_SEASON = $boxscore->{season};
986 1         2 $BOXSCORE = $boxscore;
987 1         2 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     18 ) unless $BROKEN_FILES{$boxscore->{_id}}->{BS};
994 2         6 for my $player (@{$team->{roster}}) {
  2         5  
995 43 100 66     120 next if $player->{broken} || $player->{position} eq 'N/A';
996 38         45 $PLAYER = $player;
997 38         83 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       92 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       16 unless $BROKEN_FILES{$boxscore->{_id}}->{BS};
1006             }
1007              
1008             sub test_normalized_header ($) {
1009              
1010 3     3 1 6 my $boxscore = shift;
1011              
1012 3 50       24 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         20 my_is($boxscore->{result}[1], 2, 'winner correct in result');
1018 3 50 66     40 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         35 my_like($boxscore->{date}, qr/^\d{8}$/, 'game date set correctly');
1025 3 100       25 my_ok($boxscore->{location}, 'location set') unless is_unapplicable('location');
1026 3         23 my $path = get_game_path_from_id($boxscore->{_id});
1027 3         15 for my $source (qw(BS PL RO GS ES)) {
1028             my_is($boxscore->{sources}{$source}, 1 , "source $source registered")
1029 15 100 66     252 if $source eq 'BS' || (-f "$path/$source.html" && ! $BROKEN_FILES{$boxscore->{_id}}{$source});
      100        
1030             }
1031 3         15 for my $field (qw(_id attendance last_updated month date ot start_ts stop_ts stage season season_id)) {
1032 33         106 my_like($boxscore->{$field}, qr/^\-?\d+$/, "$field a number");
1033             }
1034             }
1035              
1036             sub test_normalized_roster ($$) {
1037              
1038 6     6 1 12 my $roster = shift;
1039 6         11 my $team_name = shift;
1040              
1041 6         9 for my $player (@{$roster}) {
  6         16  
1042 106         136 for (keys %{$player}) {
  106         632  
1043 3022         4060 my $field = $_;
1044 3022         4151 when ('position') { test_position($player->{$_}, 'position ok') }
  106         183  
1045 2916         3208 when ('name') { test_name($player->{$_}, 'name ok') };
  106         176  
1046 2810         3074 when ('status') {
1047 76         201 my_like($player->{$field}, qr/^(C|A| |X)$/, 'status ok');
1048             }
1049 2734         2942 when ('start') {
1050 76         183 my_like($player->{$field}, qr/^(0|1|2)$/, 'start ok');
1051             }
1052 2658         2816 when ('plusMinus') {
1053 72         181 my_like($player->{$field}, qr/^\-?\d+$/, '+- ok');
1054             }
1055 2586         2602 when ('decision') {
1056 6 50       23 if ($player->{position} eq 'G') {
1057 6         21 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         2588 when ('team') {
1064 106         173 my_is($player->{team}, $team_name, 'team in player ok');
1065             }
1066 2474         2550 default {
1067             my_like(
1068             $player->{$field},
1069             qr/[+-]?([0-9]*[.])?[0-9]+/, "stat $field a number"
1070 2474 50       7592 ) if defined $player->{$field};
1071             }
1072             }
1073             }
1074             }
1075              
1076             sub test_normalized_teams ($) {
1077              
1078 3     3 1 6 my $boxscore = shift;
1079 3         12 for my $t (0,1) {
1080 6         16 my $team = $boxscore->{teams}[$t];
1081 6         13 for my $stat (keys %{$team->{stats}}) {
  6         31  
1082 50         141 my_like($team->{stats}{$stat}, qr/[+-]?([0-9]*[.])?[0-9]+/, "team $stat a number");
1083             }
1084 6         17 for my $field (qw(pull shots score)) {
1085 18         60 my_like($team->{$field}, qr/[+-]?([0-9]*[.])?[0-9]+/, "team $field a number");
1086             }
1087 6         24 my_ok(! exists $team->{_decision}, 'pseudo-decision removed');
1088 6         20 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     21 $BROKEN_FILES{$boxscore->{_id}}->{BS} == $UNSYNCHED;
1098 3         8 for my $event (@{$boxscore->{events}}) {
  3         13  
1099 666         1919 test_game_id($event->{game_id}, 'event has game');
1100 666 100       1785 my_like($event->{zone}, qr/^(OFF|DEF|NEU|UNK)$/, 'event has zone')
1101             unless is_noplay_event($event);
1102 666 100       1731 my_is(length($event->{strength}), 2, 'event has strength')
1103             unless is_noplay_event($event);
1104 666         1465 for my $field (qw(period season stage so ts)) {
1105             my_like($event->{$field}, qr/^\d+$/, "field $field a number")
1106 3330 100       10816 if defined $event->{$field};
1107             }
1108             test_event_coords($event)
1109 666 50       1199 if $event->{coords};
1110 666 100       1170 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       1772 if exists $event->{en};
1114             my_is(
1115             $event->{team2},
1116             $boxscore->{teams}[1-$event->{t}]{name}, 'team2 ok'
1117 666 100 100     2908 ) if defined $event->{t} && $event->{t} != -1;
1118 666         1300 for my $field (qw(player1 player2 assist1 assist2)) {
1119             test_player_id($event->{$field}, "field $field ok")
1120 2664 100       5159 if exists $event->{$field};
1121             }
1122 666 100       1132 if ($event->{on_ice}) {
1123 624         832 for my $t (0,1) {
1124 1248         1396 for my $o (@{$event->{on_ice}[$t]}) {
  1248         2573  
1125 7208         9630 test_player_id($o, 'valid player id on ice');
1126             }
1127             }
1128             }
1129 666         1117 for ($event->{type}) {
1130 666         1409 when ('GOAL') {
1131 11         34 test_player_id($event->{player1}, "goal scorer player1 ok");
1132             test_player_id($event->{player2}, "goal goalie player2 ok")
1133 11 50       55 unless $event->{en};
1134 11         34 for my $field (qw(en gwg penaltyshot)) {
1135 33         107 my_like($event->{$field}, qr/^0|1$/, "goal $field ok")
1136             }
1137 11 100       34 if ($event->{assist1}) {
1138 10         29 test_player_id($event->{assist1}, 'assist1 ok');
1139 10         39 my_is($event->{assist1}, $event->{assists}[0], 'in array');
1140 10 50       31 if ($event->{assist2}) {
1141 10         26 test_player_id($event->{assist2}, 'assist2 ok');
1142 10         38 my_is($event->{assist2}, $event->{assists}[1], 'in array');
1143             }
1144             }
1145 11         42 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         28 when ('FAC') {
1153 0         0 test_team($event->{winning_team}, 'FAC winning team ok');
1154             }
1155 11 50       32 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         57 "$event->{shot_type} shot type normalized",
1163             );
1164 11         27 my @fields = keys %{$event};
  11         133  
1165 11         27 for my $field (@fields) {
1166 325         701 my_ok(defined $field, "existing field $field defined");
1167 325 100 66     1057 next if $field eq 'file' || ref $event->{$field};
1168 279 100       581 if ($event->{$field} =~ /\D/) {
1169 88         183 my_is($event->{$field}, uc($event->{$field}), 'all UC ok');
1170             }
1171             else {
1172 191         425 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 8 my $boxscore = shift;
1183              
1184 3         7 my $gp = scalar @{$boxscore->{periods}};
  3         8  
1185 3         14 my_is($boxscore->{events}[-1]{type}, 'GEND', 'gend at the end');
1186 3         13 my_is($boxscore->{events}[-2]{type}, 'PEND', 'pend penultimate');
1187 3         9 my_is(scalar(grep{$_->{type} eq 'PSTR'} @{$boxscore->{events}}), $gp, "$gp pstr");
  666         1252  
  3         23  
1188 3         10 my_is(scalar(grep{$_->{type} eq 'PEND'} @{$boxscore->{events}}), $gp, "$gp pend");
  666         1175  
  3         9  
1189 3         11 my_is(scalar(grep{$_->{type} eq 'GEND'} @{$boxscore->{events}}), 1, '1 gend');
  666         838  
  3         9  
1190              
1191 3         8 for my $e (0..$#{$boxscore->{events}}-1) {
  3         27  
1192             my_cmp_ok(
1193             $boxscore->{events}[$e]{period},
1194             '<=',
1195             $boxscore->{events}[$e+1]{period},
1196 663         2237 '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       3113 $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     3727 $boxscore->{events}[$e+1]{ts};
1219 663         1086 my $event = $boxscore->{events}[$e];
1220 663         3134 my_like($event->{_id}, qr/^$boxscore->{_id}\d{4}$/, '_id created');
1221 663 100       2718 if ($event->{type} eq 'PSTR') {
    100          
    50          
1222 9         37 my_like($event->{ts}, qr/^(0|\d{2,3}00)$/, 'period starts at 00');
1223 9         60 my_like($event->{time}, qr/^\d+:00$/, 'period starts at :00');
1224             }
1225             elsif ($event->{type} eq 'PEND') {
1226 9         30 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 8274 my $boxscore = shift;
1237              
1238 3         12 $THIS_SEASON = $boxscore->{season};
1239 3         22 test_normalized_header($boxscore);
1240 3         14 test_normalized_teams($boxscore);
1241 3         30 test_normalized_events($boxscore);
1242 3         14 test_arranged_events($boxscore);
1243             }
1244              
1245             END {
1246 39 100   39   370939 if ($BOXSCORE) {
1247 1         3 $Data::Dumper::Varname = 'BOXSCORE';
1248             }
1249 39 50       295 if ($EVENT) {
1250 0         0 $Data::Dumper::Varname = 'EVENT';
1251 0         0 print Dumper $EVENT;
1252             }
1253 39 50       952 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