4 * A model that runs tests on the reports model,
5 * based on a special test-DSL
7 * Inherits from Reports_Model for "dammit, it's protected!" reasons
9 class Ninja_Reports_Test
extends Status_Reports_Model
11 public $test_file = false; /**< The file name we're testing */
13 public $description = false; /**< A string describing the purpose of this test */
15 private $results = array();
16 private $config_files = false;
17 public $passed = 0; /**< Number of passed tests */
18 public $failed = 0; /**< Number of failed tests */
19 private $logfiles = false;
20 private $logfile = false;
21 private $sqlfile = false;
22 private $table_name = false;
23 private $test_globals = array();
24 private $interesting_prefixes = array();
25 public $sub_reports = 0; /**< The number of sub reports */
26 private $color_red = '';
27 private $color_green = '';
28 private $color_reset = '';
29 public $db_name; /**< Database name */
30 public $db_user; /**< Database user */
31 public $db_pass; /**< Database password */
32 public $db_type; /**< Database type */
33 public $db_host; /**< Database hostname */
34 public $importer; /**< The command used to import logs into the database */
37 * Run new test file. Will parse the file, but not run it
39 public function __construct($test_file)
41 if (PHP_SAPI
=== 'cli' && posix_isatty(STDOUT
)) {
42 $this->color_red
= "\033[31m";
43 $this->color_green
= "\033[32m";
44 $this->color_reset
= "\033[0m";
50 $this->tests
= $this->parse_test($test_file);
51 $this->test_file
= $test_file;
54 private function red($str)
56 return $this->color_red
.$str.$this->color_reset
;
59 private function green($str)
61 return $this->color_green
.$str.$this->color_reset
;
64 private function verify_correct($duration, $correct)
67 $this->interesting_prefixes
= array();
69 foreach ($correct as $k => $v) {
72 $prefix = explode('_', $k);
74 if (!isset($total[$prefix]))
77 $total[$prefix] +
= $v;
78 $this->interesting_prefixes
[$prefix] = $prefix;
80 foreach ($total as $prefix => $tot) {
81 if ($tot == $duration ||
$prefix === 'TOTAL' ||
$prefix === 'PERCENT')
83 echo "Wonky 'correct' for prefix $prefix: total != duration ($tot != $duration)\n";
90 private function run_test($params)
92 $timeperiods = array();
93 foreach ($this->test_globals
as $k => $v) {
94 if ($k === 'timeperiod')
96 else if (!isset($params[$k]))
103 if (!($correct = arr
::search($params, 'correct'))) {
104 echo "No 'correct' block set for test. Bailing out\n";
107 $start_time = arr
::search($params, 'start_time');
108 $end_time = arr
::search($params, 'end_time');
109 $timeperiod = arr
::search($params, 'timeperiod');
111 $timeperiods[] =& $timeperiod;
112 unset($params['correct']);
113 unset($params['timeperiod']);
115 if (!$this->verify_correct($end_time - $start_time, $correct))
118 Old_Timeperiod_Model
::$precreated = array();
119 foreach ($timeperiods as $idx => &$tp) {
120 if (!isset($tp['timeperiod_name']))
121 $tp['timeperiod_name'] = 'the_timeperiod'.$idx;
123 $tpobj = Old_Timeperiod_Model
::instance(array('start_time' => $start_time, 'end_time' => $end_time, 'rpttimeperiod' => $tp['timeperiod_name']));
124 $tpobj->set_timeperiod_data($tp);
125 $tpobj->resolve_timeperiods();
128 $this->sub_reports
= 0;
129 $opts = new Test_report_options();
130 foreach ($params as $k => $v) {
131 if ($k == 'objects') {
132 if ($params['report_type'] == 'hosts' ||
$params['report_type'] == 'services') {
133 $this->sub_reports
= count($v);
135 if ($params['report_type'] == 'hostgroups' ||
$params['report_type'] == 'servicegroups') {
136 $opts->members
= array_merge($opts->members
, $v);
138 $this->sub_reports
= count($opts->members
);
141 if (!$opts->set($k, $v))
142 echo "Failed to set option '$k' to '$v'\n";
144 $opts->properties_copy
['rpttimeperiod']['options'][$timeperiod['timeperiod_name']] = $timeperiod['timeperiod_name'];
145 $opts['rpttimeperiod'] = $timeperiod['timeperiod_name'];
147 # force logs to be kept so we can analyze them and make
148 # sure the durations add up
149 $opts['include_trends'] = true;
151 $rpt = new Status_Reports_Model($opts, $this->table_name
);
152 $return_arr = $rpt->get_uptime();
153 $this->result
= $return_arr;
154 $this->report_objects
[$this->cur_test
] = $rpt;
160 return $this->compare_test_result($return_arr, $correct, $rpt);
163 private function parse_test($test_file = false)
168 $req = array('description', 'logfiles');
171 $buf = file_get_contents($test_file);
172 $lines = explode("\n", $buf);
174 $pushed_blocks = array();
175 $pushed_names = array();
178 foreach ($lines as $raw_line) {
180 $line = trim($raw_line);
181 if (!strlen($line) ||
$line{0} === '#')
184 if ($line{0} === '}') {
185 if (!empty($pushed_blocks)) {
186 $tmp = array_pop($pushed_blocks);
187 $tmp[$block_name] = $block;
189 $block_name = array_pop($pushed_names);
193 if ($block_name === 'global_vars')
194 $this->test_globals
= $block;
195 elseif ($block_name === 'logfiles')
196 $this->logfiles
= $block;
198 $params[$block_name] = $block;
200 $block = $block_name = false;
205 if ($line{strlen($line) - 1} === '{') {
206 $ary = preg_split("/[\t ]*{[\t ]*/", $line);
208 array_push($pushed_blocks, $block);
209 array_push($pushed_names, $block_name);
211 $block_name = $ary[0];
216 # regular variable, or possibly a single string
217 $ary = preg_split("/[\t ]*=[\t ]/", $line);
219 if (count($ary) !== 2) {
220 if ($block !== false) {
224 echo "Line $num_line in $test_file is malformed: $line\n";
230 if ($block !== false) {
236 $this->description
= $v;
239 $this->config_files
= $v;
248 $this->table_name
= $v;
252 $this->crash("Illegal variable: $k = $v\n");
261 //recurse_print($params);
262 $this->params
= $params;
267 * Run the actual test file
269 public function run_test_series()
271 echo "Preparing for test-series '" . $this->description
. "'\n";
273 $this->details
= array();
274 if ($this->sqlfile
) {
275 exec('mysql -u'.$this->db_user
.' -p'.$this->db_pass
.' '.$this->db_name
.' < '.'test/unit_test/reports/'.$this->sqlfile
);
276 $this->table_name
= substr($this->sqlfile
, 0, strpos($this->sqlfile
, '.'));
280 $this->logfiles
[] = "test/unit_test/reports/".$this->logfile
;
282 $result = $this->import_logs();
287 foreach ($this->tests
as $test_name => $params) {
288 $this->cur_test
= $test_name;
289 $result = $this->run_test($params);
290 printf(" %-7s $test_name\n", $result === true ?
$this->green('OK') : $this->red('FAILED'));
291 if ($result === true)
294 $this->details
[$test_name] = $result;
299 foreach ($this->details
as $test_name => $fail_desc) {
305 return $this->failed
;
308 private function import_logs()
310 if (!$this->logfiles
) {
311 echo "No logfiles to import\n";
314 $lfiles = join(" ", $this->logfiles
);
316 $line = exec("cat $lfiles | md5sum", $output, $retcode);
317 $ary = explode(" ", $line);
319 $table_name = substr($this->description
, 0, 20) . substr($checksum, 0, 10);
320 $table_name = preg_replace("/[^A-Za-z0-9_]/", "_", $table_name);
321 $this->table_name
= $table_name;
323 echo "Using db table '".$this->table_name
."'\n";
325 $db = Database
::instance();
327 $db->query("SELECT * FROM ".$this->table_name
." LIMIT 1");
329 catch (Kohana_Database_Exception
$e) {
334 echo "Data is cached\n";
336 if ($this->db_type
=== 'oracle')
337 $sql = "CREATE TABLE $table_name AS (SELECT * FROM report_data WHERE rownum < 0)";
339 $sql = "CREATE TABLE $table_name AS SELECT * FROM report_data LIMIT 0";
340 echo "Building table [$table_name]. This might take a moment or three...\n";
341 if( ! $db->query($sql)) {
342 $this->crash("Error creating table $table_name: ".$db->error_message());
344 echo "Importing $lfiles to '$table_name'\n";
345 $cmd = $this->importer
.
346 " --db-name=".$this->db_name
.
347 " --db-table=".$this->table_name
.
348 " --db-user=".$this->db_user
.
349 " --db-pass=".$this->db_pass
." " .
350 " --db-host=".$this->db_host
." " .
351 " --db-type=".$this->db_type
." " .
352 join(" ", $this->logfiles
).' 2>&1';
354 exec($cmd, $out, $retval);
355 echo "$cmd\n".implode("\n", $out);
357 echo "import failed. cleaning up and skipping test\n";
359 $db->query("DROP TABLE ".$this->table_name
);
367 private function count_sub_reports($top)
370 foreach ($top as $middle) {
371 if (!is_array($middle) ||
!isset($middle['states']))
373 foreach ($middle as $bottom) {
374 if (is_array($bottom) && isset($bottom['states']))
381 private function log_duration($st_log)
383 if (!is_array($st_log))
387 foreach ($st_log as $le) {
388 $duration +
= $le['duration'];
394 * compare_test_result
396 * Compare result from test with correct values
397 * @return mixed true or array with diff
400 private function compare_test_result($full_result, $correct, $rpt)
402 if (empty($full_result) ||
empty($full_result['states']))
403 $this->crash("No test result\n");
404 $states = $full_result['states'];
407 $this->crash("No \$correct\n");
410 foreach ($correct as $k => $v) {
412 foreach ($v as $sub_name => $sub_correct) {
414 foreach ($full_result as $group) {
415 if (!isset($group['states']) ||
!$group['states'])
417 foreach ($group as $obj) {
419 if (!isset($obj['states']) ||
!$obj['states'])
421 $tmp_sub_name .= $obj['states']['HOST_NAME'];
422 if (isset($obj['states']['SERVICE_DESCRIPTION']))
423 $tmp_sub_name .= ';'.$obj['states']['SERVICE_DESCRIPTION'];
424 if ($tmp_sub_name === $sub_name) {
431 $failed[$sub_name] = "expected sub report $sub_name, but couldn't find it";
434 foreach ($sub_correct as $sk => $sv) {
435 if (!isset($sub['states']) ||
!isset($sub['states'][$sk])) {
436 $failed["$sub_name;$sk"] = "expected=$sv; lib_reports=(not set)";
439 if (strcmp($sub['states'][$sk], $sv)) {
440 $failed["$sub_name;$sk"] = "expected=$sv; lib_reports={$sub['states'][$sk]}";
446 if (!isset($states[$k])) {
447 $failed[$k] = "expected=$v; lib_reports=(not set)";
450 if (strcmp($states[$k], $v)) {
451 $failed[$k] = "expected=$v; lib_reports=$states[$k]";
455 $subs = $this->count_sub_reports($full_result);
456 if ($this->sub_reports
!= $subs) {
457 if ($this->sub_reports
=== false) {
458 $failed['sub-reports'] = "There are sub-reports, but shouldn't be";
461 $failed['sub-reports'] = "Expected $this->sub_reports sub reports. Got $subs";
465 # check duration for all sub-reports individually
466 foreach ($full_result as $k => $l) {
469 foreach ($l as $k2 => $obj) {
470 if (!is_numeric($k2))
472 $duration = $this->log_duration($obj['log']);
473 if ($duration != $rpt->options
['end_time'] - $rpt->options
['start_time']) {
474 $failed['st_log ' . $k] = "Log duration doesn't match report period duration (expected ".($rpt->options
['end_time'] - $rpt->options
['start_time']).", was $duration)";
479 if (empty($failed)) {
482 foreach ($states as $k => $v) {
483 $prefix = explode('_', $k);
484 $prefix = $prefix[0];
485 if (!isset($this->interesting_prefixes
[$prefix]))
487 if ($v != 0 && empty($correct[$k])) {
488 $failed[$k] = "expected=0; lib_reports=$v";
495 private function crash($msg)
497 echo "test.php: $msg\n";