Merge "SessionManager: Add more logging when unpersisting invalid sessions"
[mediawiki.git] / tests / phpunit / includes / db / DatabaseTestHelper.php
blob9e09d58617126640ca1bf36f24a303121f8a4bb8
1 <?php
3 use MediaWiki\Tests\Unit\Libs\Rdbms\AddQuoterMock;
4 use MediaWiki\Tests\Unit\Libs\Rdbms\SQLPlatformTestHelper;
5 use Psr\Log\NullLogger;
6 use Wikimedia\ObjectCache\HashBagOStuff;
7 use Wikimedia\Rdbms\Database;
8 use Wikimedia\Rdbms\Database\DatabaseFlags;
9 use Wikimedia\Rdbms\DatabaseDomain;
10 use Wikimedia\Rdbms\FakeResultWrapper;
11 use Wikimedia\Rdbms\QueryStatus;
12 use Wikimedia\Rdbms\Replication\ReplicationReporter;
13 use Wikimedia\Rdbms\TransactionProfiler;
14 use Wikimedia\RequestTimeout\RequestTimeout;
16 /**
17 * Helper for testing the methods from the Database class
18 * @since 1.22
20 class DatabaseTestHelper extends Database {
22 /**
23 * @var string __CLASS__ of the test suite,
24 * used to determine, if the function name is passed every time to query()
26 protected string $testName;
28 /**
29 * @var string[] Array of lastSqls passed to query(),
30 * This is an array since some methods in Database can do more than one
31 * query. Cleared when calling getLastSqls().
33 protected $lastSqls = [];
35 /** @var array Stack of result maps */
36 protected $nextResMapQueue = [];
38 /** @var array|null */
39 protected $lastResMap = null;
41 /**
42 * @var string[] Array of tables to be considered as existing by tableExist()
43 * Use setExistingTables() to alter.
45 protected $tablesExists;
47 /** @var int[] */
48 protected $forcedAffectedCountQueue = [];
50 public function __construct( string $testName, array $opts = [] ) {
51 $params = $opts + [
52 'host' => null,
53 'user' => null,
54 'password' => null,
55 'dbname' => null,
56 'schema' => null,
57 'tablePrefix' => '',
58 'flags' => 0,
59 'cliMode' => true,
60 'agent' => '',
61 'serverName' => null,
62 'topologyRole' => null,
63 'srvCache' => new HashBagOStuff(),
64 'profiler' => null,
65 'trxProfiler' => new TransactionProfiler(),
66 'logger' => new NullLogger(),
67 'errorLogger' => static function ( Exception $e ) {
68 wfWarn( get_class( $e ) . ': ' . $e->getMessage() );
70 'deprecationLogger' => static function ( $msg ) {
71 wfWarn( $msg );
73 'criticalSectionProvider' =>
74 RequestTimeout::singleton()->createCriticalSectionProvider( 120 )
76 parent::__construct( $params );
78 $this->testName = $testName;
79 $this->platform = new SQLPlatformTestHelper( new AddQuoterMock() );
80 $this->flagsHolder = new DatabaseFlags( 0 );
81 $this->replicationReporter = new ReplicationReporter(
82 $params['topologyRole'],
83 $params['logger'],
84 $params['srvCache']
87 $this->currentDomain = DatabaseDomain::newUnspecified();
88 $this->open( 'localhost', 'testuser', 'password', 'testdb', null, '' );
91 /**
92 * Returns SQL queries grouped by '; '
93 * Clear the list of queries that have been done so far.
94 * @return string
96 public function getLastSqls() {
97 $lastSqls = implode( '; ', $this->lastSqls );
98 $this->lastSqls = [];
100 return $lastSqls;
103 public function setExistingTables( $tablesExists ) {
104 $this->tablesExists = (array)$tablesExists;
108 * @param mixed $res Use an array of row arrays to set row result
109 * @param int $errno Error number
110 * @param string $error Error text
111 * @param array $options
112 * - isKnownStatementRollbackError: Return value for isKnownStatementRollbackError()
114 public function forceNextResult( $res, $errno = 0, $error = '', $options = [] ) {
115 $this->nextResMapQueue[] = [
116 'res' => $res,
117 'errno' => $errno,
118 'error' => $error
119 ] + $options;
122 protected function addSql( $sql ) {
123 // clean up spaces before and after some words and the whole string
124 $this->lastSqls[] = trim( preg_replace(
125 '/\s{2,}(?=FROM|WHERE|GROUP BY|ORDER BY|LIMIT)|(?<=SELECT|INSERT|UPDATE)\s{2,}/',
126 ' ', $sql
127 ) );
130 protected function checkFunctionName( $fname ) {
131 if ( $fname === 'Wikimedia\\Rdbms\\Database::close' ) {
132 return; // no $fname parameter
135 // Handle some internal calls from the Database class
136 $check = $fname;
137 if ( preg_match(
138 '/^Wikimedia\\\\Rdbms\\\\Database::(?:query|beginIfImplied) \((.+)\)$/',
139 $fname,
141 ) ) {
142 $check = $m[1];
145 if ( !str_starts_with( $check, $this->testName ) ) {
146 throw new LogicException( 'function name does not start with test class. ' .
147 $fname . ' vs. ' . $this->testName . '. ' .
148 'Please provide __METHOD__ to database methods.' );
152 public function strencode( $s ) {
153 // Choose apos to avoid handling of escaping double quotes in quoted text
154 return str_replace( "'", "\'", $s );
157 public function query( $sql, $fname = '', $flags = 0 ) {
158 $this->checkFunctionName( $fname );
160 return parent::query( $sql, $fname, $flags );
163 public function tableExists( $table, $fname = __METHOD__ ) {
164 [ $db, $pt ] = $this->platform->getDatabaseAndTableIdentifier( $table );
165 if ( isset( $this->sessionTempTables[$db][$pt] ) ) {
166 return true; // already known to exist
169 $this->checkFunctionName( $fname );
171 return in_array( $table, (array)$this->tablesExists );
174 public function getType() {
175 return 'test';
178 public function open( $server, $user, $password, $db, $schema, $tablePrefix ) {
179 $this->conn = (object)[ 'test' ];
181 return true;
184 protected function lastInsertId() {
185 return -1;
188 public function lastErrno() {
189 return $this->lastResMap ? $this->lastResMap['errno'] : -1;
192 public function lastError() {
193 return $this->lastResMap ? $this->lastResMap['error'] : 'test';
196 protected function isKnownStatementRollbackError( $errno ) {
197 return ( $this->lastResMap['errno'] ?? 0 ) === $errno
198 ? ( $this->lastResMap['isKnownStatementRollbackError'] ?? false )
199 : false;
202 public function fieldInfo( $table, $field ) {
203 return false;
206 public function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
207 return false;
210 public function getSoftwareLink() {
211 return 'test';
214 public function getServerVersion() {
215 return 'test';
218 public function getServerInfo() {
219 return 'test';
222 public function ping( &$rtt = null ) {
223 $rtt = 0.0;
224 return true;
227 protected function closeConnection() {
228 return true;
231 public function setNextQueryAffectedRowCounts( array $counts ) {
232 $this->forcedAffectedCountQueue = $counts;
235 protected function doSingleStatementQuery( string $sql ): QueryStatus {
236 $sql = preg_replace( '< /\* .+? \*/>', '', $sql );
237 $this->addSql( $sql );
239 if ( $this->nextResMapQueue ) {
240 $this->lastResMap = array_shift( $this->nextResMapQueue );
241 if ( !$this->lastResMap['errno'] && $this->forcedAffectedCountQueue ) {
242 $count = array_shift( $this->forcedAffectedCountQueue );
243 $this->lastQueryAffectedRows = $count;
245 } else {
246 $this->lastResMap = [ 'res' => [], 'errno' => 0, 'error' => '' ];
248 $res = $this->lastResMap['res'];
250 return new QueryStatus(
251 is_bool( $res ) ? $res : new FakeResultWrapper( $res ),
252 $this->affectedRows(),
253 $this->lastError(),
254 $this->lastErrno()