Index: trunk/phase3/maintenance/testRunner.sql |
— | — | @@ -0,0 +1,35 @@ |
| 2 | +-- |
| 3 | +-- Optional tables for parserTests recording mode |
| 4 | +-- With --record option, success data will be saved to these tables, |
| 5 | +-- and comparisons of what's changed from the previous run will be |
| 6 | +-- displayed at the end of each run. |
| 7 | +-- |
| 8 | +-- These tables currently require MySQL 5 (or maybe 4.1?) for subselects. |
| 9 | +-- |
| 10 | + |
| 11 | +drop table if exists /*$wgDBprefix*/testitem; |
| 12 | +drop table if exists /*$wgDBprefix*/testrun; |
| 13 | + |
| 14 | +create table /*$wgDBprefix*/testrun ( |
| 15 | + tr_id int not null auto_increment, |
| 16 | + |
| 17 | + tr_date char(14) binary, |
| 18 | + tr_mw_version blob, |
| 19 | + tr_php_version blob, |
| 20 | + tr_db_version blob, |
| 21 | + tr_uname blob, |
| 22 | + |
| 23 | + primary key (tr_id) |
| 24 | +) engine=InnoDB; |
| 25 | + |
| 26 | +create table /*$wgDBprefix*/testitem ( |
| 27 | + ti_run int not null, |
| 28 | + ti_name varchar(255), |
| 29 | + ti_success bool, |
| 30 | + |
| 31 | + unique key (ti_run, ti_name), |
| 32 | + key (ti_run, ti_success), |
| 33 | + |
| 34 | + foreign key (ti_run) references /*$wgDBprefix*/testrun(tr_id) |
| 35 | + on delete cascade |
| 36 | +) engine=InnoDB; |
Index: trunk/phase3/maintenance/parserTests.inc |
— | — | @@ -25,7 +25,7 @@ |
26 | 26 | */ |
27 | 27 | |
28 | 28 | /** */ |
29 | | -$options = array( 'quick', 'color', 'quiet', 'help', 'show-output' ); |
| 29 | +$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record' ); |
30 | 30 | $optionsWithArgs = array( 'regex' ); |
31 | 31 | |
32 | 32 | require_once( 'commandLine.inc' ); |
— | — | @@ -98,6 +98,12 @@ |
99 | 99 | $this->regex = ''; |
100 | 100 | } |
101 | 101 | |
| 102 | + if( isset( $options['record'] ) ) { |
| 103 | + $this->recorder = new TestRecorder(); |
| 104 | + } else { |
| 105 | + $this->recorder = new DummyTestRecorder(); |
| 106 | + } |
| 107 | + |
102 | 108 | $this->hooks = array(); |
103 | 109 | $this->functionHooks = array(); |
104 | 110 | } |
— | — | @@ -138,6 +144,7 @@ |
139 | 145 | $success = 0; |
140 | 146 | $total = 0; |
141 | 147 | $n = 0; |
| 148 | + $this->recorder->start(); |
142 | 149 | while( false !== ($line = fgets( $infile ) ) ) { |
143 | 150 | $n++; |
144 | 151 | if( preg_match( '/^!!\s*(\w+)/', $line, $matches ) ) { |
— | — | @@ -205,14 +212,16 @@ |
206 | 213 | $section = null; |
207 | 214 | continue; |
208 | 215 | } |
209 | | - if( $this->runTest( |
| 216 | + $result = $this->runTest( |
210 | 217 | $this->chomp( $data['test'] ), |
211 | 218 | $this->chomp( $data['input'] ), |
212 | 219 | $this->chomp( $data['result'] ), |
213 | | - $this->chomp( $data['options'] ) ) ) { |
| 220 | + $this->chomp( $data['options'] ) ); |
| 221 | + if( $result ) { |
214 | 222 | $success++; |
215 | 223 | } |
216 | 224 | $total++; |
| 225 | + $this->recorder->record( $this->chomp( $data['test'] ), $result ); |
217 | 226 | $data = array(); |
218 | 227 | $section = null; |
219 | 228 | continue; |
— | — | @@ -227,9 +236,13 @@ |
228 | 237 | $data[$section] .= $line; |
229 | 238 | } |
230 | 239 | } |
| 240 | + $this->recorder->end(); |
| 241 | + |
| 242 | + print "\n"; |
| 243 | + $this->recorder->report(); |
231 | 244 | if( $total > 0 ) { |
232 | 245 | $ratio = wfPercent( 100 * $success / $total ); |
233 | | - print $this->termColor( 1 ) . "\nPassed $success of $total tests ($ratio) "; |
| 246 | + print $this->termColor( 1 ) . "Passed $success of $total tests ($ratio) "; |
234 | 247 | if( $success == $total ) { |
235 | 248 | print $this->termColor( 32 ) . "PASSED!"; |
236 | 249 | } else { |
— | — | @@ -827,7 +840,145 @@ |
828 | 841 | $this->termColor( 0 ); |
829 | 842 | return "$display\n$caret"; |
830 | 843 | } |
| 844 | + |
| 845 | +} |
831 | 846 | |
| 847 | +class DummyTestRecorder { |
| 848 | + function start() { |
| 849 | + // dummy |
| 850 | + } |
| 851 | + |
| 852 | + function record( $test, $result ) { |
| 853 | + // dummy |
| 854 | + } |
| 855 | + |
| 856 | + function end() { |
| 857 | + // dummy |
| 858 | + } |
| 859 | + |
| 860 | + function report() { |
| 861 | + // dummy |
| 862 | + } |
832 | 863 | } |
833 | 864 | |
| 865 | +class TestRecorder { |
| 866 | + var $db; ///< Database connection to the main DB |
| 867 | + var $curRun; ///< run ID number for the current run |
| 868 | + var $prevRun; ///< run ID number for the previous run, if any |
| 869 | + |
| 870 | + function __construct() { |
| 871 | + $this->db = wfGetDB( DB_MASTER ); |
| 872 | + } |
| 873 | + |
| 874 | + /** |
| 875 | + * Set up result recording; insert a record for the run with the date |
| 876 | + * and all that fun stuff |
| 877 | + */ |
| 878 | + function start() { |
| 879 | + $this->db->begin(); |
| 880 | + |
| 881 | + // We'll make comparisons against the previous run later... |
| 882 | + $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' ); |
| 883 | + |
| 884 | + $this->db->insert( 'testrun', |
| 885 | + array( |
| 886 | + 'tr_date' => $this->db->timestamp(), |
| 887 | + 'tr_mw_version' => SpecialVersion::getVersion(), |
| 888 | + 'tr_php_version' => phpversion(), |
| 889 | + 'tr_db_version' => $this->db->getServerVersion(), |
| 890 | + 'tr_uname' => php_uname() |
| 891 | + ), |
| 892 | + __METHOD__ ); |
| 893 | + $this->curRun = $this->db->insertId(); |
| 894 | + } |
| 895 | + |
| 896 | + /** |
| 897 | + * Record an individual test item's success or failure to the db |
| 898 | + * @param string $test |
| 899 | + * @param bool $result |
| 900 | + */ |
| 901 | + function record( $test, $result ) { |
| 902 | + $this->db->insert( 'testitem', |
| 903 | + array( |
| 904 | + 'ti_run' => $this->curRun, |
| 905 | + 'ti_name' => $test, |
| 906 | + 'ti_success' => $result ? 1 : 0, |
| 907 | + ), |
| 908 | + __METHOD__ ); |
| 909 | + } |
| 910 | + |
| 911 | + /** |
| 912 | + * Commit transaction and clean up for result recording |
| 913 | + */ |
| 914 | + function end() { |
| 915 | + $this->db->commit(); |
| 916 | + } |
| 917 | + |
| 918 | + function report() { |
| 919 | + if( $this->prevRun ) { |
| 920 | + $table = array( |
| 921 | + array( 'previously failing test(s) now PASSING! :)', 0, 1 ), |
| 922 | + array( 'previously PASSING test(s) removed o_O', 1, null ), |
| 923 | + array( 'new PASSING test(s) :)', null, 1 ), |
| 924 | + |
| 925 | + array( 'previously passing test(s) now FAILING! :(', 1, 0 ), |
| 926 | + array( 'previously FAILING test(s) removed O_o', 0, null ), |
| 927 | + array( 'new FAILING test(s) :(', null, 0 ), |
| 928 | + ); |
| 929 | + foreach( $table as $blah ) { |
| 930 | + list( $label, $before, $after ) = $blah; |
| 931 | + $count = $this->comparisonCount( $before, $after ); |
| 932 | + if( $count ) { |
| 933 | + printf( "%4d %s\n", $count, $label ); |
| 934 | + } |
| 935 | + } |
| 936 | + } else { |
| 937 | + print "No previous test runs to compare against.\n"; |
| 938 | + } |
| 939 | + } |
| 940 | + |
| 941 | + /** |
| 942 | + * :P |
| 943 | + */ |
| 944 | + private function comparisonCount( $before, $after ) { |
| 945 | + $testitem = $this->db->tableName( 'testitem' ); |
| 946 | + $prevRun = intval( $this->prevRun ); |
| 947 | + $curRun = intval( $this->curRun ); |
| 948 | + $prevStatus = $this->condition( $before ); |
| 949 | + $curStatus = $this->condition( $after ); |
| 950 | + |
| 951 | + // note: requires a current mysql for subselects |
| 952 | + if( is_null( $after ) ) { |
| 953 | + $sql = " |
| 954 | + select count(*) as c from $testitem as prev |
| 955 | + where prev.ti_run=$prevRun and |
| 956 | + prev.ti_success $prevStatus and |
| 957 | + (select current.ti_success from testitem as current |
| 958 | + where current.ti_run=$curRun |
| 959 | + and prev.ti_name=current.ti_name) $curStatus"; |
| 960 | + } else { |
| 961 | + $sql = " |
| 962 | + select count(*) as c from $testitem as current |
| 963 | + where current.ti_run=$curRun and |
| 964 | + current.ti_success $curStatus and |
| 965 | + (select prev.ti_success from testitem as prev |
| 966 | + where prev.ti_run=$prevRun |
| 967 | + and prev.ti_name=current.ti_name) $prevStatus"; |
| 968 | + } |
| 969 | + $result = $this->db->query( $sql, __METHOD__ ); |
| 970 | + $row = $this->db->fetchObject( $result ); |
| 971 | + $this->db->freeResult( $result ); |
| 972 | + return $row->c; |
| 973 | + } |
| 974 | + |
| 975 | + private function condition( $value ) { |
| 976 | + if( is_null( $value ) ) { |
| 977 | + return 'IS NULL'; |
| 978 | + } else { |
| 979 | + return '=' . intval( $value ); |
| 980 | + } |
| 981 | + } |
| 982 | + |
| 983 | +} |
| 984 | + |
834 | 985 | ?> |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -163,7 +163,12 @@ |
164 | 164 | * Approximate height for client-side scaling fallback instead of passing -1 |
165 | 165 | into the HTML output. |
166 | 166 | * Make the DNSBL to check for proxy blocking configurable via $wgSorbsUrl |
| 167 | +* Add experimental recording/reporting mode to parser tests runner, to |
| 168 | + compare changes against the previous run. |
| 169 | + Additional tables 'testrun' and 'testitem' are in maintenance/testRunner.sql, |
| 170 | + source this and pass --record option to parserTests.php |
167 | 171 | |
| 172 | + |
168 | 173 | == Languages updated == |
169 | 174 | |
170 | 175 | * Bishnupriya Manipuri (bpy) |