Index: trunk/extensions/CodeReview/codereview.css |
— | — | @@ -78,6 +78,13 @@ |
79 | 79 | color: #666; |
80 | 80 | } |
81 | 81 | |
| 82 | +.mw-codereview-success { |
| 83 | + color: #1a2; |
| 84 | +} |
| 85 | +.mw-codereview-fail { |
| 86 | + color: #d21; |
| 87 | +} |
| 88 | + |
82 | 89 | /* Diffs */ |
83 | 90 | .mw-codereview-diff ins { |
84 | 91 | text-decoration: none; |
Index: trunk/extensions/CodeReview/CodeTestRun.php |
— | — | @@ -0,0 +1,170 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeTestRun { |
| 5 | + public function __construct( CodeTestSuite $suite, $row ) { |
| 6 | + if( is_object( $row ) ) { |
| 7 | + $row = wfObjectToArray( $row ); |
| 8 | + } |
| 9 | + $this->suite = $suite; |
| 10 | + $this->id = intval( $row['ctrun_id'] ); |
| 11 | + $this->revId = intval( $row['ctrun_rev_id'] ); |
| 12 | + $this->status = $row['ctrun_status']; |
| 13 | + $this->countTotal = $row['ctrun_count_total']; |
| 14 | + $this->countSuccess = $row['ctrun_count_success']; |
| 15 | + |
| 16 | + $this->mCaseMap = null; // Lazy-initialize... |
| 17 | + } |
| 18 | + |
| 19 | + public function getResults( $success=null ) { |
| 20 | + $dbr = wfGetDB( DB_MASTER ); |
| 21 | + $conds = array( |
| 22 | + 'ctresult_run_id' => $this->id, |
| 23 | + 'ctresult_case_id=ctcase_id', |
| 24 | + ); |
| 25 | + if( $success !== null ) { |
| 26 | + $conds['ctresult_success'] = $success ? 1 : 0; |
| 27 | + } |
| 28 | + |
| 29 | + $result = $dbr->select( |
| 30 | + array( |
| 31 | + 'code_test_result', |
| 32 | + 'code_test_case', |
| 33 | + ), |
| 34 | + '*', |
| 35 | + $conds, |
| 36 | + __METHOD__ ); |
| 37 | + |
| 38 | + $out = array(); |
| 39 | + foreach( $result as $row ) { |
| 40 | + $out[] = new CodeTestResult( $this, $row ); |
| 41 | + } |
| 42 | + return $out; |
| 43 | + } |
| 44 | + |
| 45 | + public static function newFromRevId( CodeTestSuite $suite, $revId ) { |
| 46 | + $dbr = wfGetDB( DB_MASTER ); |
| 47 | + $row = $dbr->selectRow( 'code_test_run', |
| 48 | + '*', |
| 49 | + array( |
| 50 | + 'ctrun_suite_id' => $suite->id, |
| 51 | + 'ctrun_rev_id' => $revId, |
| 52 | + ), |
| 53 | + __METHOD__ ); |
| 54 | + if( $row ) { |
| 55 | + return new CodeTestRun( $suite, $row ); |
| 56 | + } else { |
| 57 | + return null; |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + public function setStatus( $status ) { |
| 62 | + $this->status = $status; |
| 63 | + $dbw = wfGetDB( DB_MASTER ); |
| 64 | + $dbw->update( |
| 65 | + 'code_test_run', |
| 66 | + array( |
| 67 | + 'ctrun_status' => $status, |
| 68 | + ), |
| 69 | + array( |
| 70 | + 'ctrun_id' => $this->id, |
| 71 | + ), |
| 72 | + __METHOD__ ); |
| 73 | + } |
| 74 | + |
| 75 | + public static function insertRun( CodeTestSuite $suite, $revId, $status, $results=array() ) { |
| 76 | + $dbw = wfGetDB( DB_MASTER ); |
| 77 | + $countTotal = count( $results ); |
| 78 | + $countSucceeded = count( array_filter( $results ) ); |
| 79 | + |
| 80 | + $insertData = array( |
| 81 | + 'ctrun_suite_id' => $suite->id, |
| 82 | + 'ctrun_rev_id' => $revId, |
| 83 | + 'ctrun_status' => $status, |
| 84 | + 'ctrun_count_total' => $countTotal, |
| 85 | + 'ctrun_count_success' => $countSucceeded, |
| 86 | + ); |
| 87 | + $dbw->insert( 'code_test_run', |
| 88 | + $insertData, |
| 89 | + __METHOD__ ); |
| 90 | + |
| 91 | + $insertData['ctrun_id'] = $dbw->insertId(); |
| 92 | + $run = new CodeTestRun( $suite, $insertData ); |
| 93 | + if( $status == 'complete' && $results ) { |
| 94 | + $run->insertData( $results ); |
| 95 | + } |
| 96 | + return $run; |
| 97 | + } |
| 98 | + |
| 99 | + public function getCaseId( $caseName ) { |
| 100 | + $this->loadCaseMap(); |
| 101 | + if( isset( $this->mCaseMap[$caseName] ) ) { |
| 102 | + return $this->mCaseMap[$caseName]; |
| 103 | + } else { |
| 104 | + $dbw = wfGetDB( DB_MASTER ); |
| 105 | + $dbw->insert( 'code_test_case', |
| 106 | + array( |
| 107 | + 'ctcase_suite_id' => $this->id, |
| 108 | + 'ctcase_name' => $caseName, |
| 109 | + ), |
| 110 | + __METHOD__ ); |
| 111 | + $id = intval( $dbw->insertId() ); |
| 112 | + $this->mCaseMap[$caseName] = $id; |
| 113 | + return $id; |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + protected function loadCaseMap() { |
| 118 | + if( is_null( $this->mCaseMap ) ) { |
| 119 | + $this->mCaseMap = array(); |
| 120 | + $dbw = wfGetDB( DB_MASTER ); |
| 121 | + $result = $dbw->select( 'code_test_case', |
| 122 | + array( |
| 123 | + 'ctcase_id', |
| 124 | + 'ctcase_name', |
| 125 | + ), |
| 126 | + array( |
| 127 | + 'ctcase_suite_id' => $this->id, |
| 128 | + ), |
| 129 | + __METHOD__ |
| 130 | + ); |
| 131 | + foreach( $result as $row ) { |
| 132 | + $this->mCaseMap[$row->ctcase_name] = intval( $row->ctcase_id ); |
| 133 | + } |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + public function saveResults( $results ) { |
| 138 | + $this->insertResults( $results ); |
| 139 | + $this->status = "complete"; |
| 140 | + $dbw = wfGetDB( DB_MASTER ); |
| 141 | + $dbw->update( |
| 142 | + 'code_test_run', |
| 143 | + array( |
| 144 | + 'ctrun_status' => $this->status, |
| 145 | + 'ctrun_count_total' => $this->countTotal, |
| 146 | + 'ctrun_count_success' => $this->countSuccess, |
| 147 | + ), |
| 148 | + array( |
| 149 | + 'ctrun_id' => $this->id, |
| 150 | + ), |
| 151 | + __METHOD__ ); |
| 152 | + } |
| 153 | + |
| 154 | + public function insertResults( $results ) { |
| 155 | + $dbw = wfGetDB( DB_MASTER ); |
| 156 | + $this->countTotal = 0; |
| 157 | + $this->countSuccess = 0; |
| 158 | + foreach( $results as $caseName => $result ) { |
| 159 | + $this->countTotal++; |
| 160 | + if( $result ) { |
| 161 | + $this->countSuccess++; |
| 162 | + } |
| 163 | + $insertData[] = array( |
| 164 | + 'ctresult_run_id' => $this->id, |
| 165 | + 'ctresult_case_id' => $this->getCaseId( $caseName ), |
| 166 | + 'ctresult_success' => $result ? 1 : 0, |
| 167 | + ); |
| 168 | + } |
| 169 | + $dbw->insert( 'code_test_result', $insertData, __METHOD__ ); |
| 170 | + } |
| 171 | +} |
Property changes on: trunk/extensions/CodeReview/CodeTestRun.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 172 | + native |
Index: trunk/extensions/CodeReview/codereview.sql |
— | — | @@ -65,7 +65,7 @@ |
66 | 66 | cr_diff mediumblob NULL, |
67 | 67 | -- Text flags: gzip,utf-8,external |
68 | 68 | cr_flags tinyblob NOT NULL, |
69 | | - |
| 69 | + |
70 | 70 | primary key (cr_repo_id, cr_id), |
71 | 71 | key (cr_repo_id, cr_timestamp), |
72 | 72 | key cr_repo_author (cr_repo_id, cr_author, cr_timestamp) |
— | — | @@ -217,3 +217,70 @@ |
218 | 218 | key cpc_repo_rev_time (cpc_repo_id, cpc_rev_id, cpc_timestamp), |
219 | 219 | key cpc_repo_time (cpc_repo_id, cpc_timestamp) |
220 | 220 | ) /*$wgDBTableOptions*/; |
| 221 | + |
| 222 | +-- |
| 223 | +-- Information on available test suites |
| 224 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_suite; |
| 225 | +CREATE TABLE /*$wgDBprefix*/code_test_suite ( |
| 226 | + -- Unique ID per test suite |
| 227 | + ctsuite_id int auto_increment not null, |
| 228 | + |
| 229 | + -- Repository ID of the code base this applies to |
| 230 | + ctsuite_repo_id int not null, |
| 231 | + |
| 232 | + -- Which branch path this applies to, eg '/trunk/phase3' |
| 233 | + ctsuite_branch_path varchar(255) not null, |
| 234 | + |
| 235 | + -- Pleasantly user-readable name, eg "ParserTests" |
| 236 | + ctsuite_name varchar(255) not null, |
| 237 | + |
| 238 | + -- Description... |
| 239 | + ctsuite_desc varchar(255) not null, |
| 240 | + |
| 241 | + primary key ctsuite_id (ctsuite_id) |
| 242 | +) /*$wgDBtableOptions*/; |
| 243 | + |
| 244 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_case; |
| 245 | +CREATE TABLE /*$wgDBprefix*/code_test_case ( |
| 246 | + ctcase_id int auto_increment not null, |
| 247 | + ctcase_suite_id int not null, |
| 248 | + ctcase_name varchar(255) not null, |
| 249 | + |
| 250 | + primary key ctc_id (ctcase_id), |
| 251 | + key (ctcase_suite_id, ctcase_id) |
| 252 | +) /*$wgDBtableOptions*/; |
| 253 | + |
| 254 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_run; |
| 255 | +CREATE TABLE /*$wgDBprefix*/code_test_run ( |
| 256 | + ctrun_id int auto_increment not null, |
| 257 | + |
| 258 | + ctrun_suite_id int not null, |
| 259 | + ctrun_rev_id int not null, |
| 260 | + |
| 261 | + ctrun_status enum ('running', 'complete', 'abort'), |
| 262 | + |
| 263 | + ctrun_count_total int, |
| 264 | + ctrun_count_success int, |
| 265 | + |
| 266 | + primary key ctrun_id (ctrun_id), |
| 267 | + key suite_rev (ctrun_suite_id, ctrun_rev_id) |
| 268 | +) /*$wgDBtableOptions*/; |
| 269 | + |
| 270 | + |
| 271 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_result; |
| 272 | +CREATE TABLE /*$wgDBprefix*/code_test_result ( |
| 273 | + ctresult_id int auto_increment not null, |
| 274 | + |
| 275 | + -- Which test run and case are we on? |
| 276 | + ctresult_run_id int not null, |
| 277 | + ctresult_case_id int not null, |
| 278 | + |
| 279 | + -- Did we succeed or fail? |
| 280 | + ctresult_success bool not null, |
| 281 | + |
| 282 | + -- Optional HTML chunk data |
| 283 | + ctresult_details blob, |
| 284 | + |
| 285 | + primary key ctr_id (ctresult_id), |
| 286 | + key run_id (ctresult_run_id, ctresult_id) |
| 287 | +) /*$wgDBtableOptions*/; |
Index: trunk/extensions/CodeReview/CodeReview.i18n.php |
— | — | @@ -28,6 +28,7 @@ |
29 | 29 | 'code-authors' => 'authors', |
30 | 30 | 'code-status' => 'states', |
31 | 31 | 'code-tags' => 'tags', |
| 32 | + 'code-tests' => 'Test cases', |
32 | 33 | 'code-authors-text' => 'Below is a list of repo authors in order of recent commits.', |
33 | 34 | 'code-author-haslink' => 'This author is linked to the wiki user $1', |
34 | 35 | 'code-author-orphan' => 'This author has no link with a wiki account', |
— | — | @@ -46,6 +47,7 @@ |
47 | 48 | 'code-field-status' => 'Status', |
48 | 49 | 'code-field-timestamp' => 'Date', |
49 | 50 | 'code-field-comments' => 'Notes', |
| 51 | + 'code-field-tests' => 'Tests', |
50 | 52 | 'code-field-path' => 'Path', |
51 | 53 | 'code-field-text' => 'Note', |
52 | 54 | 'code-field-select' => 'Select', |
Index: trunk/extensions/CodeReview/archives/codereview-code_tests.sql |
— | — | @@ -0,0 +1,66 @@ |
| 2 | +-- |
| 3 | +-- Information on available test suites |
| 4 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_suite; |
| 5 | +CREATE TABLE /*$wgDBprefix*/code_test_suite ( |
| 6 | + -- Unique ID per test suite |
| 7 | + ctsuite_id int auto_increment not null, |
| 8 | + |
| 9 | + -- Repository ID of the code base this applies to |
| 10 | + ctsuite_repo_id int not null, |
| 11 | + |
| 12 | + -- Which branch path this applies to, eg '/trunk/phase3' |
| 13 | + ctsuite_branch_path varchar(255) not null, |
| 14 | + |
| 15 | + -- Pleasantly user-readable name, eg "ParserTests" |
| 16 | + ctsuite_name varchar(255) not null, |
| 17 | + |
| 18 | + -- Description... |
| 19 | + ctsuite_desc varchar(255) not null, |
| 20 | + |
| 21 | + primary key ctsuite_id (ctsuite_id) |
| 22 | +) /*$wgDBtableOptions*/; |
| 23 | + |
| 24 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_case; |
| 25 | +CREATE TABLE /*$wgDBprefix*/code_test_case ( |
| 26 | + ctcase_id int auto_increment not null, |
| 27 | + ctcase_suite_id int not null, |
| 28 | + ctcase_name varchar(255) not null, |
| 29 | + |
| 30 | + primary key ctc_id (ctcase_id), |
| 31 | + key (ctcase_suite_id, ctcase_id) |
| 32 | +) /*$wgDBtableOptions*/; |
| 33 | + |
| 34 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_run; |
| 35 | +CREATE TABLE /*$wgDBprefix*/code_test_run ( |
| 36 | + ctrun_id int auto_increment not null, |
| 37 | + |
| 38 | + ctrun_suite_id int not null, |
| 39 | + ctrun_rev_id int not null, |
| 40 | + |
| 41 | + ctrun_status enum ('running', 'complete', 'abort'), |
| 42 | + |
| 43 | + ctrun_count_total int, |
| 44 | + ctrun_count_success int, |
| 45 | + |
| 46 | + primary key ctrun_id (ctrun_id), |
| 47 | + key suite_rev (ctrun_suite_id, ctrun_rev_id) |
| 48 | +) /*$wgDBtableOptions*/; |
| 49 | + |
| 50 | + |
| 51 | +DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_result; |
| 52 | +CREATE TABLE /*$wgDBprefix*/code_test_result ( |
| 53 | + ctresult_id int auto_increment not null, |
| 54 | + |
| 55 | + -- Which test run and case are we on? |
| 56 | + ctresult_run_id int not null, |
| 57 | + ctresult_case_id int not null, |
| 58 | + |
| 59 | + -- Did we succeed or fail? |
| 60 | + ctresult_success bool not null, |
| 61 | + |
| 62 | + -- Optional HTML chunk data |
| 63 | + ctresult_details blob, |
| 64 | + |
| 65 | + primary key ctr_id (ctresult_id), |
| 66 | + key run_id (ctresult_run_id, ctresult_id) |
| 67 | +) /*$wgDBtableOptions*/; |
Property changes on: trunk/extensions/CodeReview/archives/codereview-code_tests.sql |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 68 | + native |
Index: trunk/extensions/CodeReview/CodeRevisionListView.php |
— | — | @@ -187,7 +187,7 @@ |
188 | 188 | 'options' => array( 'GROUP BY' => 'cp_rev_id', 'USE INDEX' => array( 'code_path' => 'cp_repo_id' ) ), |
189 | 189 | 'join_conds' => array( |
190 | 190 | 'code_rev' => array( 'INNER JOIN', 'cr_repo_id = cp_repo_id AND cr_id = cp_rev_id' ), |
191 | | - 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cp_repo_id AND cc_rev_id = cp_rev_id' ) |
| 191 | + 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cp_repo_id AND cc_rev_id = cp_rev_id' ), |
192 | 192 | ) |
193 | 193 | ); |
194 | 194 | // No path; entire repo... |
— | — | @@ -198,7 +198,7 @@ |
199 | 199 | 'conds' => array( 'cr_repo_id' => $this->mRepo->getId() ), |
200 | 200 | 'options' => array( 'GROUP BY' => 'cr_id' ), |
201 | 201 | 'join_conds' => array( |
202 | | - 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ) |
| 202 | + 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ), |
203 | 203 | ) |
204 | 204 | ); |
205 | 205 | } |
— | — | @@ -206,8 +206,15 @@ |
207 | 207 | } |
208 | 208 | |
209 | 209 | function getSelectFields() { |
210 | | - return array( $this->getDefaultSort(), 'cr_status', 'COUNT( DISTINCT cc_id ) AS comments', |
211 | | - 'cr_path', 'cr_message', 'cr_author', 'cr_timestamp' ); |
| 210 | + return array( $this->getDefaultSort(), |
| 211 | + 'cr_id', |
| 212 | + 'cr_repo_id', |
| 213 | + 'cr_status', |
| 214 | + 'COUNT(DISTINCT cc_id) AS comments', |
| 215 | + 'cr_path', |
| 216 | + 'cr_message', |
| 217 | + 'cr_author', |
| 218 | + 'cr_timestamp' ); |
212 | 219 | } |
213 | 220 | |
214 | 221 | function getFieldNames() { |
— | — | @@ -215,6 +222,7 @@ |
216 | 223 | $this->getDefaultSort() => wfMsg( 'code-field-id' ), |
217 | 224 | 'cr_status' => wfMsg( 'code-field-status' ), |
218 | 225 | 'comments' => wfMsg( 'code-field-comments' ), |
| 226 | + 'tests' => wfMsg( 'code-field-tests' ), |
219 | 227 | 'cr_path' => wfMsg( 'code-field-path' ), |
220 | 228 | 'cr_message' => wfMsg( 'code-field-message' ), |
221 | 229 | 'cr_author' => wfMsg( 'code-field-author' ), |
— | — | @@ -260,6 +268,40 @@ |
261 | 269 | } else { |
262 | 270 | return intval( $value ); |
263 | 271 | } |
| 272 | + case 'tests': |
| 273 | + // fixme -- this still isn't too efficient... |
| 274 | + $rev = CodeRevision::newFromRow( $this->mRepo, $row ); |
| 275 | + $runs = $rev->getTestRuns(); |
| 276 | + if( empty( $runs ) ) { |
| 277 | + return ' '; |
| 278 | + } else { |
| 279 | + $total = 0; |
| 280 | + $success = 0; |
| 281 | + $progress = false; |
| 282 | + foreach( $runs as $run ) { |
| 283 | + $total += $run->countTotal; |
| 284 | + $success += $run->countSuccess; |
| 285 | + if( $run->status == 'running' ) { |
| 286 | + $progress = true; |
| 287 | + } |
| 288 | + } |
| 289 | + if( $progress ) { |
| 290 | + global $wgStylePath; |
| 291 | + return Xml::element( 'img', array( |
| 292 | + 'src' => "$wgStylePath/common/images/spinner.gif", |
| 293 | + 'width' => 20, |
| 294 | + 'height' => 20, |
| 295 | + 'alt' => "...", |
| 296 | + 'title' => "Tests in progress...", |
| 297 | + )); |
| 298 | + } |
| 299 | + if( $success == $total ) { |
| 300 | + $class = 'mw-codereview-success'; |
| 301 | + } else { |
| 302 | + $class = 'mw-codereview-fail'; |
| 303 | + } |
| 304 | + return "<span class='$class'><strong>$success</strong>/$total</span>"; |
| 305 | + } |
264 | 306 | case 'cr_path': |
265 | 307 | return Xml::openElement( 'div', array( 'title' => (string)$value ) ) . |
266 | 308 | $this->mView->mSkin->link( |
Index: trunk/extensions/CodeReview/CodeTestSuite.php |
— | — | @@ -0,0 +1,38 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeTestSuite { |
| 5 | + public static function newFromRow( CodeRepository $repo, $row ) { |
| 6 | + $suite = new CodeTestSuite(); |
| 7 | + $suite->id = intval( $row->ctsuite_id ); |
| 8 | + $suite->repo = $repo; |
| 9 | + $suite->repoId = $repo->getId(); |
| 10 | + $suite->branchPath = $row->ctsuite_branch_path; |
| 11 | + $suite->name = $row->ctsuite_name; |
| 12 | + $suite->desc = $row->ctsuite_desc; |
| 13 | + return $suite; |
| 14 | + } |
| 15 | + |
| 16 | + function getRun( $revId ) { |
| 17 | + return CodeTestRun::newFromRevId( $this, $revId ); |
| 18 | + } |
| 19 | + |
| 20 | + function setStatus( $revId, $status ) { |
| 21 | + $run = $this->getRun( $revId ); |
| 22 | + if( $run ) { |
| 23 | + $run->setStatus( $status ); |
| 24 | + } else { |
| 25 | + $run = CodeTestRun::insertRun( $this, $revId, $status ); |
| 26 | + } |
| 27 | + return $run; |
| 28 | + } |
| 29 | + |
| 30 | + function saveResults( $revId, $results ) { |
| 31 | + $run = $this->getRun( $revId ); |
| 32 | + if( $run ) { |
| 33 | + $run->saveResults( $results ); |
| 34 | + } else { |
| 35 | + $run = CodeTestRun::insertRun( $this, $revId, "complete", $results ); |
| 36 | + } |
| 37 | + return $run; |
| 38 | + } |
| 39 | +} |
Property changes on: trunk/extensions/CodeReview/CodeTestSuite.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 40 | + native |
Index: trunk/extensions/CodeReview/CodeRevisionView.php |
— | — | @@ -81,6 +81,12 @@ |
82 | 82 | } |
83 | 83 | |
84 | 84 | $html .= $this->formatMetaData( $fields ); |
| 85 | + # Show test case info |
| 86 | + $tests = $this->formatTests(); |
| 87 | + if( $tests ) { |
| 88 | + $html .= "<h2 id='code-tests'>" . wfMsgHtml( 'code-tests' ) . |
| 89 | + "</h2>\n" . $tests; |
| 90 | + } |
85 | 91 | # Output diff |
86 | 92 | if ( $this->mRev->isDiffable() ) { |
87 | 93 | $diffHtml = $this->formatDiff(); |
— | — | @@ -282,6 +288,42 @@ |
283 | 289 | return $this->mSkin->link( $special, htmlspecialchars( $tag ) ); |
284 | 290 | } |
285 | 291 | |
| 292 | + protected function formatTests() { |
| 293 | + $runs = $this->mRev->getTestRuns(); |
| 294 | + $html = ''; |
| 295 | + if( count( $runs ) ) { |
| 296 | + foreach( $runs as $run ) { |
| 297 | + $html .= "<h3>" . htmlspecialchars( $run->suite->name ) . "</h3>\n"; |
| 298 | + if( $run->status == 'complete' ) { |
| 299 | + $total = $run->countTotal; |
| 300 | + $success = $run->countSuccess; |
| 301 | + $failed = $total - $success; |
| 302 | + if( $failed ) { |
| 303 | + $html .= "<p><span class='mw-codereview-success'>$success</span> succeeded tests, " . |
| 304 | + "<span class='mw-codereview-fail'>$failed</span> failed tests:</p>"; |
| 305 | + |
| 306 | + $tests = $run->getResults( false ); |
| 307 | + $html .= "<ul>\n"; |
| 308 | + foreach( $tests as $test ) { |
| 309 | + $html .= "<li>" . htmlspecialchars( $test->caseName ) . "</li>\n"; |
| 310 | + } |
| 311 | + $html .= "</ul>\n"; |
| 312 | + } else { |
| 313 | + $html .= "<p><span class='mw-codereview-success'>$success</span> succeeded tests.</p>"; |
| 314 | + |
| 315 | + } |
| 316 | + } elseif( $run->status == "running" ) { |
| 317 | + $html .= "<p>Test cases are running...</p>"; |
| 318 | + } elseif( $run->status == "abort" ) { |
| 319 | + $html .= "<p>Test run aborted.</p>"; |
| 320 | + } else { |
| 321 | + // Err, this shouldn't happen? |
| 322 | + } |
| 323 | + } |
| 324 | + } |
| 325 | + return $html; |
| 326 | + } |
| 327 | + |
286 | 328 | protected function formatDiff() { |
287 | 329 | global $wgEnableAPI; |
288 | 330 | |
Index: trunk/extensions/CodeReview/CodeRevision.php |
— | — | @@ -578,6 +578,56 @@ |
579 | 579 | public function isValidTag( $tag ) { |
580 | 580 | return ( $this->normalizeTag( $tag ) !== false ); |
581 | 581 | } |
| 582 | + |
| 583 | + public function getTestRuns() { |
| 584 | + $dbr = wfGetDB( DB_SLAVE ); |
| 585 | + $result = $dbr->select( |
| 586 | + array( |
| 587 | + 'code_test_suite', |
| 588 | + 'code_test_run', |
| 589 | + ), |
| 590 | + '*', |
| 591 | + array( |
| 592 | + 'ctsuite_repo_id' => $this->mRepoId, |
| 593 | + 'ctsuite_id=ctrun_suite_id', |
| 594 | + 'ctrun_rev_id' => $this->mId, |
| 595 | + ), |
| 596 | + __METHOD__ ); |
| 597 | + $runs = array(); |
| 598 | + foreach( $result as $row ) { |
| 599 | + $suite = CodeTestSuite::newFromRow( $this->mRepo, $row ); |
| 600 | + $runs[] = new CodeTestRun( $suite, $row ); |
| 601 | + } |
| 602 | + return $runs; |
| 603 | + } |
| 604 | + |
| 605 | + public function getTestResults( $success = null ) { |
| 606 | + $dbr = wfGetDB( DB_SLAVE ); |
| 607 | + $conds = array( |
| 608 | + 'ctr_repo_id' => $this->mRepoId, |
| 609 | + 'ctr_rev_id' => $this->mId, |
| 610 | + 'ctr_case_id=ctc_id', |
| 611 | + 'ctc_suite_id=cts_id' ); |
| 612 | + if( $success === true ) { |
| 613 | + $conds['ctr_result'] = 1; |
| 614 | + } elseif( $success === false ) { |
| 615 | + $conds['ctr_result'] = 0; |
| 616 | + } |
| 617 | + $results = $dbr->select( |
| 618 | + array( |
| 619 | + 'code_test_result', |
| 620 | + 'code_test_case', |
| 621 | + 'code_test_suite', |
| 622 | + ), |
| 623 | + '*', |
| 624 | + $conds, |
| 625 | + __METHOD__ ); |
| 626 | + $out = array(); |
| 627 | + foreach( $results as $row ) { |
| 628 | + $out[] = new CodeTestResult( $row ); |
| 629 | + } |
| 630 | + return $out; |
| 631 | + } |
582 | 632 | |
583 | 633 | public function getPrevious() { |
584 | 634 | // hack! |
Index: trunk/extensions/CodeReview/CodeReview.php |
— | — | @@ -40,6 +40,7 @@ |
41 | 41 | $wgAutoloadClasses['ApiCodeUpdate'] = $dir . 'api/ApiCodeUpdate.php'; |
42 | 42 | $wgAutoloadClasses['ApiCodeDiff'] = $dir . 'api/ApiCodeDiff.php'; |
43 | 43 | $wgAutoloadClasses['ApiCodeComments'] = $dir . 'api/ApiCodeComments.php'; |
| 44 | +$wgAutoloadClasses['ApiCodeTestUpload'] = $dir . 'api/ApiCodeTestUpload.php'; |
44 | 45 | |
45 | 46 | $wgAutoloadClasses['CodeDiffHighlighter'] = $dir . 'DiffHighlighter.php'; |
46 | 47 | $wgAutoloadClasses['CodeRepository'] = $dir . 'CodeRepository.php'; |
— | — | @@ -66,6 +67,10 @@ |
67 | 68 | $wgAutoloadClasses['CodeView'] = $dir . 'SpecialCode.php'; |
68 | 69 | $wgAutoloadClasses['SpecialRepoAdmin'] = $dir . 'SpecialRepoAdmin.php'; |
69 | 70 | |
| 71 | +$wgAutoloadClasses['CodeTestSuite'] = $dir . 'CodeTestSuite.php'; |
| 72 | +$wgAutoloadClasses['CodeTestRun'] = $dir . 'CodeTestRun.php'; |
| 73 | +$wgAutoloadClasses['CodeTestResult'] = $dir . 'CodeTestResult.php'; |
| 74 | + |
70 | 75 | $wgSpecialPages['Code'] = 'SpecialCode'; |
71 | 76 | $wgSpecialPageGroups['Code'] = 'developer'; |
72 | 77 | $wgSpecialPages['RepoAdmin'] = 'SpecialRepoAdmin'; |
— | — | @@ -73,6 +78,7 @@ |
74 | 79 | |
75 | 80 | $wgAPIModules['codeupdate'] = 'ApiCodeUpdate'; |
76 | 81 | $wgAPIModules['codediff'] = 'ApiCodeDiff'; |
| 82 | +$wgAPIModules['codetestupload'] = 'ApiCodeTestUpload'; |
77 | 83 | $wgAPIListModules['codecomments'] = 'ApiCodeComments'; |
78 | 84 | |
79 | 85 | $wgExtensionMessagesFiles['CodeReview'] = $dir . 'CodeReview.i18n.php'; |
— | — | @@ -129,6 +135,10 @@ |
130 | 136 | // What images can be used for client-side side-by-side comparisons? |
131 | 137 | $wgCodeReviewImgRegex = '/\.(png|jpg|jpeg|gif)$/i'; |
132 | 138 | |
| 139 | +// Set to a secret string for HMAC validation of test run data uploads. |
| 140 | +// Should match test runner's $wgParserTestRemote['secret']. |
| 141 | +$wgCodeReviewSharedSecret = false; |
| 142 | + |
133 | 143 | # Schema changes |
134 | 144 | $wgHooks['LoadExtensionSchemaUpdates'][] = 'efCodeReviewSchemaUpdates'; |
135 | 145 | |
— | — | @@ -141,6 +151,7 @@ |
142 | 152 | $wgExtNewIndexes[] = array( 'code_relations', 'repo_to_from', "$base/archives/code_relations_index.sql" ); |
143 | 153 | //$wgExtNewFields[] = array( 'code_rev', "$base/archives/codereview-cr_status.sql" ); // FIXME FIXME this is a change to options... don't know how |
144 | 154 | $wgExtNewTables[] = array( 'code_bugs', "$base/archives/code_bugs.sql" ); |
| 155 | + $wgExtNewTables[] = array( 'code_test_suite', "$base/archives/codereview-code_tests.sql" ); |
145 | 156 | } elseif( $wgDBtype == 'postgres' ) { |
146 | 157 | // TODO |
147 | 158 | } |
Index: trunk/extensions/CodeReview/CodeTestResult.php |
— | — | @@ -0,0 +1,11 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class CodeTestResult { |
| 5 | + function __construct( CodeTestRun $run, $row ) { |
| 6 | + $this->run = $run; |
| 7 | + $this->id = $row->ctresult_id; |
| 8 | + $this->caseId = $row->ctresult_case_id; |
| 9 | + $this->caseName = $row->ctcase_name; |
| 10 | + $this->success = (bool)$row->ctresult_success; |
| 11 | + } |
| 12 | +} |
Property changes on: trunk/extensions/CodeReview/CodeTestResult.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 13 | + native |
Index: trunk/extensions/CodeReview/CodeRepository.php |
— | — | @@ -170,6 +170,26 @@ |
171 | 171 | throw new MWException( 'Failed to load expected revision data' ); |
172 | 172 | return CodeRevision::newFromRow( $this, $row ); |
173 | 173 | } |
| 174 | + |
| 175 | + /** |
| 176 | + * Load test suite information |
| 177 | + */ |
| 178 | + public function getTestSuite( $name ) { |
| 179 | + $dbr = wfGetDB( DB_SLAVE ); |
| 180 | + $row = $dbr->selectRow( 'code_test_suite', |
| 181 | + '*', |
| 182 | + array( |
| 183 | + 'ctsuite_repo_id' => $this->getId(), |
| 184 | + 'ctsuite_name' => $name, |
| 185 | + ), |
| 186 | + __METHOD__ |
| 187 | + ); |
| 188 | + if( $row ) { |
| 189 | + return CodeTestSuite::newFromRow( $this, $row ); |
| 190 | + } else { |
| 191 | + return null; |
| 192 | + } |
| 193 | + } |
174 | 194 | |
175 | 195 | /** |
176 | 196 | * @param int $rev Revision ID |
Index: trunk/extensions/CodeReview/api/ApiCodeTestUpload.php |
— | — | @@ -0,0 +1,119 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class ApiCodeTestUpload extends ApiBase { |
| 5 | + |
| 6 | + public function execute() { |
| 7 | + global $wgUser; |
| 8 | + // Before doing anything at all, let's check permissions |
| 9 | + if( !$wgUser->isAllowed('codereview-use') ) { |
| 10 | + $this->dieUsage('You don\'t have permission to upload test results', 'permissiondenied'); |
| 11 | + } |
| 12 | + $params = $this->extractRequestParams(); |
| 13 | + |
| 14 | + $this->validateParams( $params ); |
| 15 | + $this->validateHmac( $params ); |
| 16 | + |
| 17 | + $repo = CodeRepository::newFromName( $params['repo'] ); |
| 18 | + if( !$repo ) { |
| 19 | + $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' ); |
| 20 | + } |
| 21 | + |
| 22 | + $suite = $repo->getTestSuite( $params['suite'] ); |
| 23 | + if( !$suite ) { |
| 24 | + $this->dieUsage( "Invalid test suite ``{$params['suite']}''", 'invalidsuite' ); |
| 25 | + } |
| 26 | + |
| 27 | + // Note that we might be testing a revision that hasn't gotten slurped in yet, |
| 28 | + // so we won't reject data for revisions we don't know about yet. |
| 29 | + $revId = intval( $params['rev'] ); |
| 30 | + |
| 31 | + $status = $params['status']; |
| 32 | + if( $status == 'running' || $status == 'aborted' ) { |
| 33 | + // Set the 'tests running' flag so we can mark it... |
| 34 | + $suite->setStatus( $revId, $status ); |
| 35 | + } elseif( $status == 'complete' ) { |
| 36 | + // Save data and mark running test as completed. |
| 37 | + $results = json_decode( $params['results'], true ); |
| 38 | + if( !is_array( $results ) ) { |
| 39 | + $this->dieUsage( "Invalid test result data", 'invalidresults' ); |
| 40 | + } |
| 41 | + $suite->saveResults( $revId, $results ); |
| 42 | + } |
| 43 | + } |
| 44 | + |
| 45 | + protected function validateParams( $params ) { |
| 46 | + $required = array( 'repo', 'suite', 'rev', 'status', 'hmac' ); |
| 47 | + if( isset( $params['status'] ) && $params['status'] == 'complete' ) { |
| 48 | + $required[] = 'results'; |
| 49 | + } |
| 50 | + foreach( $required as $arg ) { |
| 51 | + if ( !isset( $params[$arg] ) ) { |
| 52 | + $this->dieUsageMsg( array( 'missingparam', $arg ) ); |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + protected function validateHmac( $params ) { |
| 58 | + global $wgCodeReviewSharedSecret; |
| 59 | + |
| 60 | + // Generate a hash MAC to validate our credentials |
| 61 | + $message = array( |
| 62 | + $params['repo'], |
| 63 | + $params['suite'], |
| 64 | + $params['rev'], |
| 65 | + $params['status'], |
| 66 | + ); |
| 67 | + if( $params['status'] == "complete" ) { |
| 68 | + $message[] = $params['results']; |
| 69 | + } |
| 70 | + $hmac = hash_hmac( "sha1", implode( "|", $message ), $wgCodeReviewSharedSecret ); |
| 71 | + if( $hmac != $params['hmac'] ) { |
| 72 | + $this->dieUsageMsg( array( 'invalidhmac', $params['hmac'] ) ); |
| 73 | + } |
| 74 | + } |
| 75 | + |
| 76 | + public function mustBePosted() { |
| 77 | + // Discourage casual browsing :) |
| 78 | + return true; |
| 79 | + } |
| 80 | + |
| 81 | + public function isWriteMode() { |
| 82 | + return true; |
| 83 | + } |
| 84 | + |
| 85 | + public function getAllowedParams() { |
| 86 | + return array( |
| 87 | + 'repo' => null, |
| 88 | + 'suite' => null, |
| 89 | + 'rev' => array( |
| 90 | + ApiBase::PARAM_TYPE => 'integer', |
| 91 | + ApiBase::PARAM_MIN => 1 |
| 92 | + ), |
| 93 | + 'status' => array( |
| 94 | + ApiBase::PARAM_TYPE => array( 'running', 'complete', 'abort' ), |
| 95 | + ), |
| 96 | + 'hmac' => null, |
| 97 | + 'results' => null, |
| 98 | + ); |
| 99 | + } |
| 100 | + |
| 101 | + public function getParamDescription() { |
| 102 | + return array( |
| 103 | + 'repo' => 'Name of repository to update', |
| 104 | + 'suite' => 'Name of test suite to record run results for', |
| 105 | + 'rev' => 'Revision ID tests were run against', |
| 106 | + 'status' => 'Status of test run', |
| 107 | + 'hmac' => 'HMAC validation', |
| 108 | + 'results' => 'JSON-encoded map of test names to success results, for status "complete"', |
| 109 | + ); |
| 110 | + } |
| 111 | + |
| 112 | + public function getDescription() { |
| 113 | + return array( |
| 114 | + 'Upload CodeReview test run results from a test runner.' ); |
| 115 | + } |
| 116 | + |
| 117 | + public function getVersion() { |
| 118 | + return __CLASS__ . ': $Id: ApiCodeUpdate.php 48928 2009-03-27 18:41:20Z catrope $'; |
| 119 | + } |
| 120 | +} |
Property changes on: trunk/extensions/CodeReview/api/ApiCodeTestUpload.php |
___________________________________________________________________ |
Name: svn:eol-style |
1 | 121 | + native |