Skip to content

Commit 9d29454

Browse files
committed
test: Correctly check if database was used in a test
Signed-off-by: provokateurin <kate@provokateurin.de>
1 parent 8536298 commit 9d29454

1 file changed

Lines changed: 40 additions & 47 deletions

File tree

tests/lib/TestCase.php

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
use OC\App\AppStore\Fetcher\AppFetcher;
1212
use OC\Command\QueueBus;
13+
use OC\DB\Connection;
1314
use OC\Files\AppData\Factory;
1415
use OC\Files\Cache\Storage;
1516
use OC\Files\Config\MountProviderCollection;
@@ -42,23 +43,13 @@
4243
abstract class TestCase extends \PHPUnit\Framework\TestCase {
4344
private QueueBus $commandBus;
4445

45-
/** @psalm-suppress ImpureStaticProperty For tests it's not an issue */
46-
protected static ?IDBConnection $realDatabase = null;
4746
/** @psalm-suppress ImpureStaticProperty */
48-
private static bool $wasDatabaseAllowed = false;
4947
protected array $services = [];
5048

5149
#[\Override]
5250
protected function onNotSuccessfulTest(\Throwable $t): never {
5351
$this->restoreAllServices();
5452

55-
// restore database connection
56-
if (!$this->IsDatabaseAccessAllowed()) {
57-
\OC::$server->registerService(IDBConnection::class, function () {
58-
return self::$realDatabase;
59-
});
60-
}
61-
6253
parent::onNotSuccessfulTest($t);
6354
}
6455

@@ -134,38 +125,37 @@ protected function setUp(): void {
134125
$this->overwriteService('AsyncCommandBus', $this->commandBus);
135126
$this->overwriteService(IBus::class, $this->commandBus);
136127

137-
// detect database access
138-
self::$wasDatabaseAllowed = true;
139-
if (!$this->IsDatabaseAccessAllowed()) {
140-
self::$wasDatabaseAllowed = false;
141-
if (is_null(self::$realDatabase)) {
142-
self::$realDatabase = Server::get(IDBConnection::class);
143-
}
144-
/** @psalm-suppress InternalMethod */
145-
\OC::$server->registerService(IDBConnection::class, function (): void {
146-
$this->fail('Your test case is not allowed to access the database.');
147-
});
148-
}
149-
150128
$traits = $this->getTestTraits();
151129
foreach ($traits as $trait) {
152130
$methodName = 'setUp' . basename(str_replace('\\', '/', $trait));
153131
if (method_exists($this, $methodName)) {
154132
call_user_func([$this, $methodName]);
155133
}
156134
}
135+
136+
// Something outside of our test might have done something with the database already, but we ignore it.
137+
$this->resetDatabaseStats();
157138
}
158139

159140
#[\Override]
160141
protected function tearDown(): void {
161142
$this->restoreAllServices();
162143

163-
// restore database connection
164-
if (!$this->IsDatabaseAccessAllowed()) {
165-
/** @psalm-suppress InternalMethod */
166-
\OC::$server->registerService(IDBConnection::class, function () {
167-
return self::$realDatabase;
168-
});
144+
$databaseAllowed = $this->IsDatabaseAccessAllowed();
145+
$databaseUsed = $this->wasDatabaseUsed();
146+
$this->resetDatabaseStats();
147+
if ($databaseUsed && !$databaseAllowed) {
148+
$this->fail('The database was used, but it was not allowed');
149+
}
150+
// Some test methods using a data provider generate cases that both use and not use the DB
151+
// Therefore we can't enable this check all the time.
152+
// It's useful for manually checking if all tests are using the correct group though.
153+
// 1. DB_UNUSED=1 ./autotest.sh sqlite | grep -B 1 "The database was not used, but it was allowed" | grep "::" | cut -d ":" -f 1 | cut -d " " -f 2 | sort -u
154+
// 2. Remove the Group attribute from all classes from the output
155+
// 3. ./autotest.sh sqlite | grep -B 1 "The database was used, but it was not allowed" | grep "::" | cut -d " " -f 2 | sort -u
156+
// 4. Add the Group attribute to all methods from the output
157+
if (!$databaseUsed && $databaseAllowed && getenv('DB_UNUSED') !== false) {
158+
$this->fail('The database was not used, but it was allowed');
169159
}
170160

171161
// further cleanup
@@ -298,26 +288,17 @@ public function filterClassMethods(string $className, array $filterMethods): arr
298288

299289
#[\Override]
300290
public static function tearDownAfterClass(): void {
301-
if (!self::$wasDatabaseAllowed && self::$realDatabase !== null) {
302-
// in case an error is thrown in a test, PHPUnit jumps straight to tearDownAfterClass,
303-
// so we need the database again
304-
\OC::$server->registerService(IDBConnection::class, function () {
305-
return self::$realDatabase;
306-
});
307-
}
308291
$dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data-autotest');
309-
if (self::$wasDatabaseAllowed && Server::get(IDBConnection::class)) {
310-
$db = Server::get(IDBConnection::class);
311-
if ($db->inTransaction()) {
312-
$db->rollBack();
313-
throw new \Exception('There was a transaction still in progress and needed to be rolled back. Please fix this in your test.');
314-
}
315-
$queryBuilder = $db->getQueryBuilder();
316-
317-
self::tearDownAfterClassCleanShares($queryBuilder);
318-
self::tearDownAfterClassCleanStorages($queryBuilder);
319-
self::tearDownAfterClassCleanFileCache($queryBuilder);
292+
$db = Server::get(IDBConnection::class);
293+
if ($db->inTransaction()) {
294+
$db->rollBack();
295+
self::fail('There was a transaction still in progress and needed to be rolled back. Please fix this in your test.');
320296
}
297+
298+
$queryBuilder = $db->getQueryBuilder();
299+
self::tearDownAfterClassCleanShares($queryBuilder);
300+
self::tearDownAfterClassCleanStorages($queryBuilder);
301+
self::tearDownAfterClassCleanFileCache($queryBuilder);
321302
self::tearDownAfterClassCleanStrayDataFiles($dataDir);
322303
self::tearDownAfterClassCleanStrayHooks();
323304
self::tearDownAfterClassCleanStrayLocks();
@@ -556,4 +537,16 @@ protected function IsDatabaseAccessAllowed(): bool {
556537
$annotations = $this->getGroupAnnotations();
557538
return in_array('DB', $annotations) || in_array('SLOWDB', $annotations);
558539
}
540+
541+
private function wasDatabaseUsed(): bool {
542+
$connection = Server::get(Connection::class);
543+
$databaseStats = $connection->getStats();
544+
return $databaseStats['built'] > 0 || $databaseStats['executed'] > 0;
545+
}
546+
547+
private function resetDatabaseStats(): void {
548+
$connection = Server::get(Connection::class);
549+
self::invokePrivate($connection, 'queriesBuilt', [0]);
550+
self::invokePrivate($connection, 'queriesExecuted', [0]);
551+
}
559552
}

0 commit comments

Comments
 (0)