3 require_once('db.php');
5 function float_to_time($time_in_float) {
6 $time = new DateTime();
7 $time->setTimestamp(floatval($time_in_float));
11 function add_builder($db, $master, $builder_name) {
12 if (!in_array($master, config('masters')))
15 return $db->select_or_insert_row('builders', NULL, array('master' => $master, 'name' => $builder_name));
18 function add_build($db, $builder_id, $build_number) {
19 return $db->select_or_insert_row('builds', NULL, array('builder' => $builder_id, 'number' => $build_number));
22 function add_slave($db, $name) {
23 return $db->select_or_insert_row('slaves', NULL, array('name' => $name));
26 function fetch_and_parse_test_results_json($url, $jsonp = FALSE) {
27 $json_contents = file_get_contents($url);
32 $json_contents = preg_replace('/^\w+\(|\);$/', '', $json_contents);
34 return json_decode($json_contents, true);
37 function store_test_results($db, $test_results, $build_id, $start_time, $end_time, $slave_id) {
38 $db->begin_transaction();
41 recursively_add_test_results($db, $build_id, $test_results['tests'], '');
43 $db->query_and_get_affected_rows(
44 'UPDATE builds SET (start_time, end_time, slave) = (least($1, start_time), greatest($2, end_time), $3) WHERE id = $4',
45 array($start_time->format('Y-m-d H:i:s.u'), $end_time->format('Y-m-d H:i:s.u'), $slave_id, $build_id));
46 $db->commit_transaction();
47 } catch (Exception $e) {
48 $db->rollback_transaction();
55 function recursively_add_test_results($db, $build_id, $tests, $full_name) {
56 if (!array_key_exists('expected', $tests) and !array_key_exists('actual', $tests)) {
57 $prefix = $full_name ? $full_name . '/' : '';
58 foreach ($tests as $name => $subtests) {
59 require_format('test_name', $name, '/^[A-Za-z0-9 +_\-\.]+$/');
60 recursively_add_test_results($db, $build_id, $subtests, $prefix . $name);
65 require_format('expected_result', $tests['expected'], '/^[A-Za-z \+]+$/');
66 require_format('actual_result', $tests['actual'], '/^[A-Za-z \+]+$/');
67 require_format('test_time', $tests['time'], '/^\d*$/');
68 $modifiers = array_get($tests, 'modifiers');
70 require_format('test_modifiers', $modifiers, '/^[A-Za-z0-9 \.\/]+$/');
73 $category = 'LayoutTest'; // FIXME: Support other test categories.
75 $test_id = $db->select_or_insert_row('tests', NULL,
76 array('name' => $full_name),
77 array('name' => $full_name, 'reftest_type' => json_encode(array_get($tests, 'reftest_type')), 'category' => $category));
79 $db->insert_row('results', NULL, array('test' => $test_id, 'build' => $build_id,
80 'expected' => $tests['expected'], 'actual' => $tests['actual'],
81 'time' => $tests['time'], 'modifiers' => $tests['modifiers']));
84 date_default_timezone_set('UTC');
85 function parse_revisions_array($postgres_array) {
86 // e.g. {"(WebKit,131456,\"2012-10-16 14:53:00\")","(Safari,162004,)"}
87 $outer_array = json_decode('[' . trim($postgres_array, '{}') . ']');
89 foreach ($outer_array as $item) {
90 $name_and_revision = explode(',', trim($item, '()'));
91 $time = strtotime(trim($name_and_revision[2], '"')) * 1000;
92 $revisions[trim($name_and_revision[0], '"')] = array(trim($name_and_revision[1], '"'), $time);
97 function format_result($result) {
98 return array('buildTime' => strtotime($result['start_time']) * 1000,
99 'revisions' => parse_revisions_array($result['revisions']),
100 'slave' => $result['slave'],
101 'buildNumber' => $result['number'],
102 'actual' => $result['actual'],
103 'expected' => $result['expected'],
104 'time' => $result['time'],
105 'modifiers' => $result['modifiers']);
108 abstract class ResultsJSONWriter {
110 private $emitted_results;
112 public function __construct($fp) {
114 $this->emitted_results = FALSE;
117 public function start($builder_id) {
118 fwrite($this->fp, "{\"status\": \"OK\", \"builders\": {\"$builder_id\":{");
121 public function end($total_time) {
122 fwrite($this->fp, "}}, \"totalGenerationTime\": $total_time}");
125 public function add_results_for_test_if_matches($current_test, $current_results) {
126 if (!count($current_results) || $this->pass_for_failure_type($current_results))
128 // FIXME: Why do we need to check the count?
130 $prefix = $this->emitted_results ? ",\n" : "";
131 fwrite($this->fp, "$prefix\"$current_test\":");
132 fwrite($this->fp, json_encode($current_results, true));
134 $this->emitted_results = TRUE;
137 abstract protected function pass_for_failure_type(&$results);
140 class FlakyResultsJSONWriter extends ResultsJSONWriter {
141 public function __construct($fp) { parent::__construct($fp); }
142 protected function pass_for_failure_type(&$results) {
143 $last_index = count($results) - 1;
144 for ($i = 1; $i < $last_index; $i++) {
145 $previous_actual = $results[$i - 1]['actual'];
146 $next_actual = $results[$i + 1]['actual'];
147 if ($previous_actual == $next_actual && $results[$i]['actual'] != $previous_actual) {
148 $results[$i]['oneOffChange'] = TRUE;
156 class WrongExpectationsResultsJSONWriter extends ResultsJSONWriter {
157 public function __construct($fp) { parent::__construct($fp); }
158 protected function pass_for_failure_type(&$results) {
159 $latest_expected_result = $results[0]['expected'];
160 $latest_actual_result = $results[0]['actual'];
162 if ($latest_expected_result == $latest_actual_result)
165 $tokens = explode(' ', $latest_expected_result);
166 return array_search($latest_actual_result, $tokens) !== FALSE
167 || (($latest_actual_result == 'TEXT' || $latest_actual_result == 'TEXT+IMAGE') && array_search('FAIL', $tokens) !== FALSE);
171 class FailingResultsJSONWriter extends WrongExpectationsResultsJSONWriter {
172 public function __construct($fp) { parent::__construct($fp); }
173 protected function pass_for_failure_type(&$results) {
174 return $results[0]['actual'] == 'PASS' || parent::pass_for_failure_type($results);
178 class ResultsJSONGenerator {
182 const MAXIMUM_NUMBER_OF_DAYS = 30;
184 public function __construct($db, $builder_id)
187 $this->builder_id = $builder_id;
190 public function generate()
192 $start_time = microtime(true);
194 if (!$this->builder_id)
197 $number_of_days = self::MAXIMUM_NUMBER_OF_DAYS;
198 $all_results = $this->db->query(
199 "SELECT results.*, builds.* FROM results
200 JOIN (SELECT builds.*, array_agg((build_revisions.repository, build_revisions.value, build_revisions.time)) AS revisions,
201 max(build_revisions.time) AS latest_revision_time
202 FROM builds, build_revisions
203 WHERE build_revisions.build = builds.id AND builds.builder = $1 AND builds.start_time > now() - interval '$number_of_days days'
204 GROUP BY builds.id) as builds ON results.build = builds.id
205 ORDER BY results.test, latest_revision_time DESC", array($this->builder_id));
209 $failing_json_fp = $this->open_json_for_failure_type('failing');
211 $flaky_json_fp = $this->open_json_for_failure_type('flaky');
213 $wrongexpectations_json_fp = $this->open_json_for_failure_type('wrongexpectations');
215 return $this->write_jsons($all_results, array(
216 new FailingResultsJSONWriter($failing_json_fp),
217 new FlakyResultsJSONWriter($flaky_json_fp),
218 new WrongExpectationsResultsJSONWriter($wrongexpectations_json_fp)), $start_time);
219 } catch (Exception $exception) {
220 fclose($wrongexpectations_json_fp);
223 } catch (Exception $exception) {
224 fclose($flaky_json_fp);
227 } catch (Exception $exception) {
228 fclose($failing_json_fp);
234 private function open_json_for_failure_type($failure_type) {
235 $failing_json_path = configPath('dataDirectory', $this->builder_id . "-$failure_type.json");
236 if (!$failing_json_path)
237 exit_with_error('FailedToDetermineResultsJSONPath', array('builderId' => $this->builder_id, 'failureType' => $failure_type));
238 $fp = fopen($failing_json_path, 'w');
240 exit_with_error('FailedToOpenResultsJSON', array('builderId' => $this->builder_id, 'failureType' => $failure_type));
244 private function write_jsons($all_results, $writers, $start_time) {
245 foreach ($writers as $writer)
246 $writer->start($this->builder_id);
247 $current_test = NULL;
248 $current_results = array();
249 while ($result = $this->db->fetch_next_row($all_results)) {
250 if ($result['test'] != $current_test) {
252 foreach ($writers as $writer)
253 $writer->add_results_for_test_if_matches($current_test, $current_results);
255 $current_results = array();
256 $current_test = $result['test'];
258 array_push($current_results, format_result($result));
261 $total_time = microtime(true) - $start_time;
262 foreach ($writers as $writer)
263 $writer->end($total_time);