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 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($current_test, $current_results) {
126 if (!count($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;
138 class ResultsJSONGenerator {
142 public function __construct($db, $builder_id)
145 $this->builder_id = $builder_id;
148 public function generate($failure_type)
150 $start_time = microtime(true);
152 if (!$this->builder_id)
155 switch ($failure_type) {
157 $test_rows = $this->db->query_and_fetch_all("SELECT DISTINCT(results.test) FROM results,
158 (SELECT builds.id FROM builds WHERE builds.builder = $1 GROUP BY builds.id LIMIT 500) as builds
159 WHERE results.build = builds.id AND results.is_flaky is TRUE",
160 array($this->builder_id));
162 case 'wrongexpectations':
163 // FIXME: three replace here shouldn't be necessary. Do it in webkitpy or report.php at latest.
164 $test_rows = $this->db->query_and_fetch_all("SELECT results.test FROM results WHERE results.build = $1
165 AND NOT string_to_array(expected, ' ') >=
166 string_to_array(replace(replace(replace(actual, 'TEXT', 'FAIL'), 'AUDIO', 'FAIL'), 'IMAGE+TEXT', 'FAIL'), ' ')",
167 array($this->latest_build()));
176 $comma_separated_test_ids = '';
177 foreach ($test_rows as $row) {
178 if ($comma_separated_test_ids)
179 $comma_separated_test_ids .= ', ';
180 $comma_separated_test_ids .= intval($row['test']);
183 $all_results = $this->db->query(
184 "SELECT results.*, builds.* FROM results
185 JOIN (SELECT builds.*, array_agg((build_revisions.repository, build_revisions.value, build_revisions.time)) AS revisions
186 FROM builds, build_revisions
187 WHERE build_revisions.build = builds.id AND builds.builder = $1
188 GROUP BY builds.id LIMIT 500) as builds ON results.build = builds.id
189 WHERE results.test in ($comma_separated_test_ids)
190 ORDER BY results.test DESC", array($this->builder_id));
194 $json_fp = $this->open_json_for_failure_type($failure_type);
196 return $this->write_jsons($all_results, new ResultsJSONWriter($json_fp), $start_time);
197 } catch (Exception $exception) {
204 private function latest_build() {
205 $results = $this->db->query_and_fetch_all('SELECT builds.id, max(build_revisions.time) AS latest_revision_time
206 FROM builds, build_revisions
207 WHERE build_revisions.build = builds.id AND builds.builder = $1
209 ORDER BY latest_revision_time DESC LIMIT 1', array($this->builder_id));
212 return $results[0]['id'];
215 private function open_json_for_failure_type($failure_type) {
216 $failing_json_path = configPath('dataDirectory', $this->builder_id . "-$failure_type.json");
217 if (!$failing_json_path)
218 exit_with_error('FailedToDetermineResultsJSONPath', array('builderId' => $this->builder_id, 'failureType' => $failure_type));
219 $fp = fopen($failing_json_path, 'w');
221 exit_with_error('FailedToOpenResultsJSON', array('builderId' => $this->builder_id, 'failureType' => $failure_type));
225 private function write_jsons($all_results, $writer, $start_time) {
226 $writer->start($this->builder_id);
227 $current_test = NULL;
228 $current_results = array();
229 while ($result = $this->db->fetch_next_row($all_results)) {
230 if ($result['test'] != $current_test) {
232 $writer->add_results_for_test($current_test, $current_results);
233 $current_results = array();
234 $current_test = $result['test'];
236 array_push($current_results, format_result($result));
238 $writer->end(microtime(true) - $start_time);
243 function update_flakiness_for_build($db, $preceeding_build, $current_build, $succeeding_build) {
244 return $db->query_and_get_affected_rows("UPDATE results
245 SET is_flaky = preceeding_results.actual = succeeding_results.actual AND preceeding_results.actual != results.actual
246 FROM results preceeding_results, results succeeding_results
247 WHERE preceeding_results.build = $1 AND results.build = $2 AND succeeding_results.build = $3
248 AND preceeding_results.test = results.test AND succeeding_results.test = results.test
249 AND results.is_flaky != (preceeding_results.actual = succeeding_results.actual AND preceeding_results.actual != results.actual)",
250 array($preceeding_build['id'], $current_build['id'], $succeeding_build['id']));
253 function update_flakiness_after_inserting_build($db, $build_id) {
254 // FIXME: In theory, it's possible for new builds to be inserted between the time this select query is ran and quries are executed by update_flakiness_for_build.
255 $ordered_builds = $db->query_and_fetch_all("SELECT builds.id, max(build_revisions.time) AS latest_revision_time
256 FROM builds, build_revisions
257 WHERE build_revisions.build = builds.id AND builds.builder = (SELECT builds.builder FROM builds WHERE id = $1)
258 GROUP BY builds.id ORDER BY latest_revision_time, builds.start_time DESC", array($build_id));
260 $current_build = NULL;
261 for ($i = 0; $i < count($ordered_builds); $i++) {
262 if ($ordered_builds[$i]['id'] == $build_id) {
267 if ($current_build === NULL)
271 if ($current_build >= 2)
272 $affected_rows += update_flakiness_for_build($db, $ordered_builds[$current_build - 2], $ordered_builds[$current_build - 1], $ordered_builds[$current_build]);
274 if ($current_build >= 1 && $current_build + 1 < count($ordered_builds))
275 $affected_rows += update_flakiness_for_build($db, $ordered_builds[$current_build - 1], $ordered_builds[$current_build], $ordered_builds[$current_build + 1]);
277 if ($current_build + 2 < count($ordered_builds))
278 $affected_rows += update_flakiness_for_build($db, $ordered_builds[$current_build], $ordered_builds[$current_build + 1], $ordered_builds[$current_build + 2]);
280 return $affected_rows;