Completed
Push — master ( 4c2e71...4c5d1a )
by
unknown
42:29
created
tests/lib/TestCase.php 1 patch
Indentation   +599 added lines, -599 removed lines patch added patch discarded remove patch
@@ -37,607 +37,607 @@
 block discarded – undo
37 37
 use PHPUnit\Framework\Attributes\Group;
38 38
 
39 39
 if (version_compare(\PHPUnit\Runner\Version::id(), 10, '>=')) {
40
-	trait OnNotSuccessfulTestTrait {
41
-		protected function onNotSuccessfulTest(\Throwable $t): never {
42
-			$this->restoreAllServices();
43
-
44
-			// restore database connection
45
-			if (!$this->IsDatabaseAccessAllowed()) {
46
-				\OC::$server->registerService(IDBConnection::class, function () {
47
-					return self::$realDatabase;
48
-				});
49
-			}
50
-
51
-			parent::onNotSuccessfulTest($t);
52
-		}
53
-	}
40
+    trait OnNotSuccessfulTestTrait {
41
+        protected function onNotSuccessfulTest(\Throwable $t): never {
42
+            $this->restoreAllServices();
43
+
44
+            // restore database connection
45
+            if (!$this->IsDatabaseAccessAllowed()) {
46
+                \OC::$server->registerService(IDBConnection::class, function () {
47
+                    return self::$realDatabase;
48
+                });
49
+            }
50
+
51
+            parent::onNotSuccessfulTest($t);
52
+        }
53
+    }
54 54
 } else {
55
-	trait OnNotSuccessfulTestTrait {
56
-		protected function onNotSuccessfulTest(\Throwable $t): void {
57
-			$this->restoreAllServices();
58
-
59
-			// restore database connection
60
-			if (!$this->IsDatabaseAccessAllowed()) {
61
-				\OC::$server->registerService(IDBConnection::class, function () {
62
-					return self::$realDatabase;
63
-				});
64
-			}
65
-
66
-			parent::onNotSuccessfulTest($t);
67
-		}
68
-	}
55
+    trait OnNotSuccessfulTestTrait {
56
+        protected function onNotSuccessfulTest(\Throwable $t): void {
57
+            $this->restoreAllServices();
58
+
59
+            // restore database connection
60
+            if (!$this->IsDatabaseAccessAllowed()) {
61
+                \OC::$server->registerService(IDBConnection::class, function () {
62
+                    return self::$realDatabase;
63
+                });
64
+            }
65
+
66
+            parent::onNotSuccessfulTest($t);
67
+        }
68
+    }
69 69
 }
70 70
 
71 71
 abstract class TestCase extends \PHPUnit\Framework\TestCase {
72
-	/** @var \OC\Command\QueueBus */
73
-	private $commandBus;
74
-
75
-	/** @var IDBConnection */
76
-	protected static $realDatabase = null;
77
-
78
-	/** @var bool */
79
-	private static $wasDatabaseAllowed = false;
80
-
81
-	/** @var array */
82
-	protected $services = [];
83
-
84
-	use OnNotSuccessfulTestTrait;
85
-
86
-	/**
87
-	 * @param string $name
88
-	 * @param mixed $newService
89
-	 * @return bool
90
-	 */
91
-	public function overwriteService(string $name, $newService): bool {
92
-		if (isset($this->services[$name])) {
93
-			return false;
94
-		}
95
-
96
-		try {
97
-			$this->services[$name] = Server::get($name);
98
-		} catch (QueryException $e) {
99
-			$this->services[$name] = false;
100
-		}
101
-		$container = \OC::$server->getAppContainerForService($name);
102
-		$container = $container ?? \OC::$server;
103
-
104
-		$container->registerService($name, function () use ($newService) {
105
-			return $newService;
106
-		});
107
-
108
-		return true;
109
-	}
110
-
111
-	/**
112
-	 * @param string $name
113
-	 * @return bool
114
-	 */
115
-	public function restoreService(string $name): bool {
116
-		if (isset($this->services[$name])) {
117
-			$oldService = $this->services[$name];
118
-
119
-			$container = \OC::$server->getAppContainerForService($name);
120
-			$container = $container ?? \OC::$server;
121
-
122
-			if ($oldService !== false) {
123
-				$container->registerService($name, function () use ($oldService) {
124
-					return $oldService;
125
-				});
126
-			} else {
127
-				unset($container[$oldService]);
128
-			}
129
-
130
-
131
-			unset($this->services[$name]);
132
-			return true;
133
-		}
134
-
135
-		return false;
136
-	}
137
-
138
-	public function restoreAllServices() {
139
-		if (!empty($this->services)) {
140
-			if (!empty($this->services)) {
141
-				foreach ($this->services as $name => $service) {
142
-					$this->restoreService($name);
143
-				}
144
-			}
145
-		}
146
-	}
147
-
148
-	protected function getTestTraits() {
149
-		$traits = [];
150
-		$class = $this;
151
-		do {
152
-			$traits = array_merge(class_uses($class), $traits);
153
-		} while ($class = get_parent_class($class));
154
-		foreach ($traits as $trait => $same) {
155
-			$traits = array_merge(class_uses($trait), $traits);
156
-		}
157
-		$traits = array_unique($traits);
158
-		return array_filter($traits, function ($trait) {
159
-			return substr($trait, 0, 5) === 'Test\\';
160
-		});
161
-	}
162
-
163
-	protected function setUp(): void {
164
-		// overwrite the command bus with one we can run ourselves
165
-		$this->commandBus = new QueueBus();
166
-		$this->overwriteService('AsyncCommandBus', $this->commandBus);
167
-		$this->overwriteService(IBus::class, $this->commandBus);
168
-
169
-		// detect database access
170
-		self::$wasDatabaseAllowed = true;
171
-		if (!$this->IsDatabaseAccessAllowed()) {
172
-			self::$wasDatabaseAllowed = false;
173
-			if (is_null(self::$realDatabase)) {
174
-				self::$realDatabase = Server::get(IDBConnection::class);
175
-			}
176
-			\OC::$server->registerService(IDBConnection::class, function (): void {
177
-				$this->fail('Your test case is not allowed to access the database.');
178
-			});
179
-		}
180
-
181
-		$traits = $this->getTestTraits();
182
-		foreach ($traits as $trait) {
183
-			$methodName = 'setUp' . basename(str_replace('\\', '/', $trait));
184
-			if (method_exists($this, $methodName)) {
185
-				call_user_func([$this, $methodName]);
186
-			}
187
-		}
188
-	}
189
-
190
-	protected function tearDown(): void {
191
-		$this->restoreAllServices();
192
-
193
-		// restore database connection
194
-		if (!$this->IsDatabaseAccessAllowed()) {
195
-			\OC::$server->registerService(IDBConnection::class, function () {
196
-				return self::$realDatabase;
197
-			});
198
-		}
199
-
200
-		// further cleanup
201
-		$hookExceptions = \OC_Hook::$thrownExceptions;
202
-		\OC_Hook::$thrownExceptions = [];
203
-		Server::get(ILockingProvider::class)->releaseAll();
204
-		if (!empty($hookExceptions)) {
205
-			throw $hookExceptions[0];
206
-		}
207
-
208
-		// fail hard if xml errors have not been cleaned up
209
-		$errors = libxml_get_errors();
210
-		libxml_clear_errors();
211
-		if (!empty($errors)) {
212
-			self::assertEquals([], $errors, 'There have been xml parsing errors');
213
-		}
214
-
215
-		if ($this->IsDatabaseAccessAllowed()) {
216
-			Storage::getGlobalCache()->clearCache();
217
-		}
218
-
219
-		// tearDown the traits
220
-		$traits = $this->getTestTraits();
221
-		foreach ($traits as $trait) {
222
-			$methodName = 'tearDown' . basename(str_replace('\\', '/', $trait));
223
-			if (method_exists($this, $methodName)) {
224
-				call_user_func([$this, $methodName]);
225
-			}
226
-		}
227
-	}
228
-
229
-	/**
230
-	 * Allows us to test private methods/properties
231
-	 *
232
-	 * @param $object
233
-	 * @param $methodName
234
-	 * @param array $parameters
235
-	 * @return mixed
236
-	 */
237
-	protected static function invokePrivate($object, $methodName, array $parameters = []) {
238
-		if (is_string($object)) {
239
-			$className = $object;
240
-		} else {
241
-			$className = get_class($object);
242
-		}
243
-		$reflection = new \ReflectionClass($className);
244
-
245
-		if ($reflection->hasMethod($methodName)) {
246
-			$method = $reflection->getMethod($methodName);
247
-
248
-			$method->setAccessible(true);
249
-
250
-			return $method->invokeArgs($object, $parameters);
251
-		} elseif ($reflection->hasProperty($methodName)) {
252
-			$property = $reflection->getProperty($methodName);
253
-
254
-			$property->setAccessible(true);
255
-
256
-			if (!empty($parameters)) {
257
-				if ($property->isStatic()) {
258
-					$property->setValue(null, array_pop($parameters));
259
-				} else {
260
-					$property->setValue($object, array_pop($parameters));
261
-				}
262
-			}
263
-
264
-			if (is_object($object)) {
265
-				return $property->getValue($object);
266
-			}
267
-
268
-			return $property->getValue();
269
-		} elseif ($reflection->hasConstant($methodName)) {
270
-			return $reflection->getConstant($methodName);
271
-		}
272
-
273
-		return false;
274
-	}
275
-
276
-	/**
277
-	 * Returns a unique identifier as uniqid() is not reliable sometimes
278
-	 *
279
-	 * @param string $prefix
280
-	 * @param int $length
281
-	 * @return string
282
-	 */
283
-	protected static function getUniqueID($prefix = '', $length = 13) {
284
-		return $prefix . Server::get(ISecureRandom::class)->generate(
285
-			$length,
286
-			// Do not use dots and slashes as we use the value for file names
287
-			ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER
288
-		);
289
-	}
290
-
291
-	/**
292
-	 * Filter methods
293
-	 *
294
-	 * Returns all methods of the given class,
295
-	 * that are public or abstract and not in the ignoreMethods list,
296
-	 * to be able to fill onlyMethods() with an inverted list.
297
-	 *
298
-	 * @param string $className
299
-	 * @param string[] $filterMethods
300
-	 * @return string[]
301
-	 */
302
-	public function filterClassMethods(string $className, array $filterMethods): array {
303
-		$class = new \ReflectionClass($className);
304
-
305
-		$methods = [];
306
-		foreach ($class->getMethods() as $method) {
307
-			if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $filterMethods, true)) {
308
-				$methods[] = $method->getName();
309
-			}
310
-		}
311
-
312
-		return $methods;
313
-	}
314
-
315
-	public static function tearDownAfterClass(): void {
316
-		if (!self::$wasDatabaseAllowed && self::$realDatabase !== null) {
317
-			// in case an error is thrown in a test, PHPUnit jumps straight to tearDownAfterClass,
318
-			// so we need the database again
319
-			\OC::$server->registerService(IDBConnection::class, function () {
320
-				return self::$realDatabase;
321
-			});
322
-		}
323
-		$dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data-autotest');
324
-		if (self::$wasDatabaseAllowed && Server::get(IDBConnection::class)) {
325
-			$db = Server::get(IDBConnection::class);
326
-			if ($db->inTransaction()) {
327
-				$db->rollBack();
328
-				throw new \Exception('There was a transaction still in progress and needed to be rolled back. Please fix this in your test.');
329
-			}
330
-			$queryBuilder = $db->getQueryBuilder();
331
-
332
-			self::tearDownAfterClassCleanShares($queryBuilder);
333
-			self::tearDownAfterClassCleanStorages($queryBuilder);
334
-			self::tearDownAfterClassCleanFileCache($queryBuilder);
335
-		}
336
-		self::tearDownAfterClassCleanStrayDataFiles($dataDir);
337
-		self::tearDownAfterClassCleanStrayHooks();
338
-		self::tearDownAfterClassCleanStrayLocks();
339
-
340
-		// Ensure we start with fresh instances of some classes to reduce side-effects between tests
341
-		unset(\OC::$server[\OC\Files\AppData\Factory::class]);
342
-		unset(\OC::$server[\OC\App\AppStore\Fetcher\AppFetcher::class]);
343
-		unset(\OC::$server[\OC\Installer::class]);
344
-		unset(\OC::$server[\OC\Updater::class]);
345
-
346
-		/** @var SetupManager $setupManager */
347
-		$setupManager = Server::get(SetupManager::class);
348
-		$setupManager->tearDown();
349
-
350
-		/** @var MountProviderCollection $mountProviderCollection */
351
-		$mountProviderCollection = Server::get(MountProviderCollection::class);
352
-		$mountProviderCollection->clearProviders();
353
-
354
-		/** @var IConfig $config */
355
-		$config = Server::get(IConfig::class);
356
-		$mountProviderCollection->registerProvider(new CacheMountProvider($config));
357
-		$mountProviderCollection->registerHomeProvider(new LocalHomeMountProvider());
358
-		$objectStoreConfig = Server::get(PrimaryObjectStoreConfig::class);
359
-		$mountProviderCollection->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
360
-
361
-		$setupManager->setupRoot();
362
-
363
-		parent::tearDownAfterClass();
364
-	}
365
-
366
-	/**
367
-	 * Remove all entries from the share table
368
-	 *
369
-	 * @param IQueryBuilder $queryBuilder
370
-	 */
371
-	protected static function tearDownAfterClassCleanShares(IQueryBuilder $queryBuilder) {
372
-		$queryBuilder->delete('share')
373
-			->executeStatement();
374
-	}
375
-
376
-	/**
377
-	 * Remove all entries from the storages table
378
-	 *
379
-	 * @param IQueryBuilder $queryBuilder
380
-	 */
381
-	protected static function tearDownAfterClassCleanStorages(IQueryBuilder $queryBuilder) {
382
-		$queryBuilder->delete('storages')
383
-			->executeStatement();
384
-	}
385
-
386
-	/**
387
-	 * Remove all entries from the filecache table
388
-	 *
389
-	 * @param IQueryBuilder $queryBuilder
390
-	 */
391
-	protected static function tearDownAfterClassCleanFileCache(IQueryBuilder $queryBuilder) {
392
-		$queryBuilder->delete('filecache')
393
-			->runAcrossAllShards()
394
-			->executeStatement();
395
-	}
396
-
397
-	/**
398
-	 * Remove all unused files from the data dir
399
-	 *
400
-	 * @param string $dataDir
401
-	 */
402
-	protected static function tearDownAfterClassCleanStrayDataFiles($dataDir) {
403
-		$knownEntries = [
404
-			'nextcloud.log' => true,
405
-			'audit.log' => true,
406
-			'owncloud.db' => true,
407
-			'.ocdata' => true,
408
-			'..' => true,
409
-			'.' => true,
410
-		];
411
-
412
-		if ($dh = opendir($dataDir)) {
413
-			while (($file = readdir($dh)) !== false) {
414
-				if (!isset($knownEntries[$file])) {
415
-					self::tearDownAfterClassCleanStrayDataUnlinkDir($dataDir . '/' . $file);
416
-				}
417
-			}
418
-			closedir($dh);
419
-		}
420
-	}
421
-
422
-	/**
423
-	 * Recursive delete files and folders from a given directory
424
-	 *
425
-	 * @param string $dir
426
-	 */
427
-	protected static function tearDownAfterClassCleanStrayDataUnlinkDir($dir) {
428
-		if ($dh = @opendir($dir)) {
429
-			while (($file = readdir($dh)) !== false) {
430
-				if (Filesystem::isIgnoredDir($file)) {
431
-					continue;
432
-				}
433
-				$path = $dir . '/' . $file;
434
-				if (is_dir($path)) {
435
-					self::tearDownAfterClassCleanStrayDataUnlinkDir($path);
436
-				} else {
437
-					@unlink($path);
438
-				}
439
-			}
440
-			closedir($dh);
441
-		}
442
-		@rmdir($dir);
443
-	}
444
-
445
-	/**
446
-	 * Clean up the list of hooks
447
-	 */
448
-	protected static function tearDownAfterClassCleanStrayHooks() {
449
-		\OC_Hook::clear();
450
-	}
451
-
452
-	/**
453
-	 * Clean up the list of locks
454
-	 */
455
-	protected static function tearDownAfterClassCleanStrayLocks() {
456
-		Server::get(ILockingProvider::class)->releaseAll();
457
-	}
458
-
459
-	/**
460
-	 * Login and setup FS as a given user,
461
-	 * sets the given user as the current user.
462
-	 *
463
-	 * @param string $user user id or empty for a generic FS
464
-	 */
465
-	protected static function loginAsUser($user = '') {
466
-		self::logout();
467
-		Filesystem::tearDown();
468
-		\OC_User::setUserId($user);
469
-		$userObject = Server::get(IUserManager::class)->get($user);
470
-		if (!is_null($userObject)) {
471
-			$userObject->updateLastLoginTimestamp();
472
-		}
473
-		\OC_Util::setupFS($user);
474
-		if (Server::get(IUserManager::class)->userExists($user)) {
475
-			\OC::$server->getUserFolder($user);
476
-		}
477
-	}
478
-
479
-	/**
480
-	 * Logout the current user and tear down the filesystem.
481
-	 */
482
-	protected static function logout() {
483
-		\OC_Util::tearDownFS();
484
-		\OC_User::setUserId('');
485
-		// needed for fully logout
486
-		Server::get(IUserSession::class)->setUser(null);
487
-	}
488
-
489
-	/**
490
-	 * Run all commands pushed to the bus
491
-	 */
492
-	protected function runCommands() {
493
-		// get the user for which the fs is setup
494
-		$view = Filesystem::getView();
495
-		if ($view) {
496
-			[, $user] = explode('/', $view->getRoot());
497
-		} else {
498
-			$user = null;
499
-		}
500
-
501
-		\OC_Util::tearDownFS(); // command can't reply on the fs being setup
502
-		$this->commandBus->run();
503
-		\OC_Util::tearDownFS();
504
-
505
-		if ($user) {
506
-			\OC_Util::setupFS($user);
507
-		}
508
-	}
509
-
510
-	/**
511
-	 * Check if the given path is locked with a given type
512
-	 *
513
-	 * @param View $view view
514
-	 * @param string $path path to check
515
-	 * @param int $type lock type
516
-	 * @param bool $onMountPoint true to check the mount point instead of the
517
-	 *                           mounted storage
518
-	 *
519
-	 * @return boolean true if the file is locked with the
520
-	 *                 given type, false otherwise
521
-	 */
522
-	protected function isFileLocked($view, $path, $type, $onMountPoint = false) {
523
-		// Note: this seems convoluted but is necessary because
524
-		// the format of the lock key depends on the storage implementation
525
-		// (in our case mostly md5)
526
-
527
-		if ($type === ILockingProvider::LOCK_SHARED) {
528
-			// to check if the file has a shared lock, try acquiring an exclusive lock
529
-			$checkType = ILockingProvider::LOCK_EXCLUSIVE;
530
-		} else {
531
-			// a shared lock cannot be set if exclusive lock is in place
532
-			$checkType = ILockingProvider::LOCK_SHARED;
533
-		}
534
-		try {
535
-			$view->lockFile($path, $checkType, $onMountPoint);
536
-			// no exception, which means the lock of $type is not set
537
-			// clean up
538
-			$view->unlockFile($path, $checkType, $onMountPoint);
539
-			return false;
540
-		} catch (LockedException $e) {
541
-			// we could not acquire the counter-lock, which means
542
-			// the lock of $type was in place
543
-			return true;
544
-		}
545
-	}
546
-
547
-	protected function getGroupAnnotations(): array {
548
-		if (method_exists($this, 'getAnnotations')) {
549
-			$annotations = $this->getAnnotations();
550
-			return $annotations['class']['group'] ?? [];
551
-		}
552
-
553
-		$r = new \ReflectionClass($this);
554
-		$doc = $r->getDocComment();
555
-
556
-		if (class_exists(Group::class)) {
557
-			$attributes = array_map(function (\ReflectionAttribute $attribute) {
558
-				/** @var Group $group */
559
-				$group = $attribute->newInstance();
560
-				return $group->name();
561
-			}, $r->getAttributes(Group::class));
562
-			if (count($attributes) > 0) {
563
-				return $attributes;
564
-			}
565
-		}
566
-		preg_match_all('#@group\s+(.*?)\n#s', $doc, $annotations);
567
-		return $annotations[1] ?? [];
568
-	}
569
-
570
-	protected function IsDatabaseAccessAllowed(): bool {
571
-		$annotations = $this->getGroupAnnotations();
572
-		if (isset($annotations)) {
573
-			if (in_array('DB', $annotations) || in_array('SLOWDB', $annotations)) {
574
-				return true;
575
-			}
576
-		}
577
-
578
-		return false;
579
-	}
580
-
581
-	/**
582
-	 * @param string $expectedHtml
583
-	 * @param string $template
584
-	 * @param array $vars
585
-	 */
586
-	protected function assertTemplate($expectedHtml, $template, $vars = []) {
587
-		$requestToken = 12345;
588
-		/** @var Defaults|\PHPUnit\Framework\MockObject\MockObject $l10n */
589
-		$theme = $this->getMockBuilder('\OCP\Defaults')
590
-			->disableOriginalConstructor()->getMock();
591
-		$theme->expects($this->any())
592
-			->method('getName')
593
-			->willReturn('Nextcloud');
594
-		/** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */
595
-		$l10n = $this->getMockBuilder(IL10N::class)
596
-			->disableOriginalConstructor()->getMock();
597
-		$l10n
598
-			->expects($this->any())
599
-			->method('t')
600
-			->willReturnCallback(function ($text, $parameters = []) {
601
-				return vsprintf($text, $parameters);
602
-			});
603
-
604
-		$t = new Base($template, $requestToken, $l10n, $theme);
605
-		$buf = $t->fetchPage($vars);
606
-		$this->assertHtmlStringEqualsHtmlString($expectedHtml, $buf);
607
-	}
608
-
609
-	/**
610
-	 * @param string $expectedHtml
611
-	 * @param string $actualHtml
612
-	 * @param string $message
613
-	 */
614
-	protected function assertHtmlStringEqualsHtmlString($expectedHtml, $actualHtml, $message = '') {
615
-		$expected = new DOMDocument();
616
-		$expected->preserveWhiteSpace = false;
617
-		$expected->formatOutput = true;
618
-		$expected->loadHTML($expectedHtml);
619
-
620
-		$actual = new DOMDocument();
621
-		$actual->preserveWhiteSpace = false;
622
-		$actual->formatOutput = true;
623
-		$actual->loadHTML($actualHtml);
624
-		$this->removeWhitespaces($actual);
625
-
626
-		$expectedHtml1 = $expected->saveHTML();
627
-		$actualHtml1 = $actual->saveHTML();
628
-		self::assertEquals($expectedHtml1, $actualHtml1, $message);
629
-	}
630
-
631
-
632
-	private function removeWhitespaces(DOMNode $domNode) {
633
-		foreach ($domNode->childNodes as $node) {
634
-			if ($node->hasChildNodes()) {
635
-				$this->removeWhitespaces($node);
636
-			} else {
637
-				if ($node instanceof \DOMText && $node->isWhitespaceInElementContent()) {
638
-					$domNode->removeChild($node);
639
-				}
640
-			}
641
-		}
642
-	}
72
+    /** @var \OC\Command\QueueBus */
73
+    private $commandBus;
74
+
75
+    /** @var IDBConnection */
76
+    protected static $realDatabase = null;
77
+
78
+    /** @var bool */
79
+    private static $wasDatabaseAllowed = false;
80
+
81
+    /** @var array */
82
+    protected $services = [];
83
+
84
+    use OnNotSuccessfulTestTrait;
85
+
86
+    /**
87
+     * @param string $name
88
+     * @param mixed $newService
89
+     * @return bool
90
+     */
91
+    public function overwriteService(string $name, $newService): bool {
92
+        if (isset($this->services[$name])) {
93
+            return false;
94
+        }
95
+
96
+        try {
97
+            $this->services[$name] = Server::get($name);
98
+        } catch (QueryException $e) {
99
+            $this->services[$name] = false;
100
+        }
101
+        $container = \OC::$server->getAppContainerForService($name);
102
+        $container = $container ?? \OC::$server;
103
+
104
+        $container->registerService($name, function () use ($newService) {
105
+            return $newService;
106
+        });
107
+
108
+        return true;
109
+    }
110
+
111
+    /**
112
+     * @param string $name
113
+     * @return bool
114
+     */
115
+    public function restoreService(string $name): bool {
116
+        if (isset($this->services[$name])) {
117
+            $oldService = $this->services[$name];
118
+
119
+            $container = \OC::$server->getAppContainerForService($name);
120
+            $container = $container ?? \OC::$server;
121
+
122
+            if ($oldService !== false) {
123
+                $container->registerService($name, function () use ($oldService) {
124
+                    return $oldService;
125
+                });
126
+            } else {
127
+                unset($container[$oldService]);
128
+            }
129
+
130
+
131
+            unset($this->services[$name]);
132
+            return true;
133
+        }
134
+
135
+        return false;
136
+    }
137
+
138
+    public function restoreAllServices() {
139
+        if (!empty($this->services)) {
140
+            if (!empty($this->services)) {
141
+                foreach ($this->services as $name => $service) {
142
+                    $this->restoreService($name);
143
+                }
144
+            }
145
+        }
146
+    }
147
+
148
+    protected function getTestTraits() {
149
+        $traits = [];
150
+        $class = $this;
151
+        do {
152
+            $traits = array_merge(class_uses($class), $traits);
153
+        } while ($class = get_parent_class($class));
154
+        foreach ($traits as $trait => $same) {
155
+            $traits = array_merge(class_uses($trait), $traits);
156
+        }
157
+        $traits = array_unique($traits);
158
+        return array_filter($traits, function ($trait) {
159
+            return substr($trait, 0, 5) === 'Test\\';
160
+        });
161
+    }
162
+
163
+    protected function setUp(): void {
164
+        // overwrite the command bus with one we can run ourselves
165
+        $this->commandBus = new QueueBus();
166
+        $this->overwriteService('AsyncCommandBus', $this->commandBus);
167
+        $this->overwriteService(IBus::class, $this->commandBus);
168
+
169
+        // detect database access
170
+        self::$wasDatabaseAllowed = true;
171
+        if (!$this->IsDatabaseAccessAllowed()) {
172
+            self::$wasDatabaseAllowed = false;
173
+            if (is_null(self::$realDatabase)) {
174
+                self::$realDatabase = Server::get(IDBConnection::class);
175
+            }
176
+            \OC::$server->registerService(IDBConnection::class, function (): void {
177
+                $this->fail('Your test case is not allowed to access the database.');
178
+            });
179
+        }
180
+
181
+        $traits = $this->getTestTraits();
182
+        foreach ($traits as $trait) {
183
+            $methodName = 'setUp' . basename(str_replace('\\', '/', $trait));
184
+            if (method_exists($this, $methodName)) {
185
+                call_user_func([$this, $methodName]);
186
+            }
187
+        }
188
+    }
189
+
190
+    protected function tearDown(): void {
191
+        $this->restoreAllServices();
192
+
193
+        // restore database connection
194
+        if (!$this->IsDatabaseAccessAllowed()) {
195
+            \OC::$server->registerService(IDBConnection::class, function () {
196
+                return self::$realDatabase;
197
+            });
198
+        }
199
+
200
+        // further cleanup
201
+        $hookExceptions = \OC_Hook::$thrownExceptions;
202
+        \OC_Hook::$thrownExceptions = [];
203
+        Server::get(ILockingProvider::class)->releaseAll();
204
+        if (!empty($hookExceptions)) {
205
+            throw $hookExceptions[0];
206
+        }
207
+
208
+        // fail hard if xml errors have not been cleaned up
209
+        $errors = libxml_get_errors();
210
+        libxml_clear_errors();
211
+        if (!empty($errors)) {
212
+            self::assertEquals([], $errors, 'There have been xml parsing errors');
213
+        }
214
+
215
+        if ($this->IsDatabaseAccessAllowed()) {
216
+            Storage::getGlobalCache()->clearCache();
217
+        }
218
+
219
+        // tearDown the traits
220
+        $traits = $this->getTestTraits();
221
+        foreach ($traits as $trait) {
222
+            $methodName = 'tearDown' . basename(str_replace('\\', '/', $trait));
223
+            if (method_exists($this, $methodName)) {
224
+                call_user_func([$this, $methodName]);
225
+            }
226
+        }
227
+    }
228
+
229
+    /**
230
+     * Allows us to test private methods/properties
231
+     *
232
+     * @param $object
233
+     * @param $methodName
234
+     * @param array $parameters
235
+     * @return mixed
236
+     */
237
+    protected static function invokePrivate($object, $methodName, array $parameters = []) {
238
+        if (is_string($object)) {
239
+            $className = $object;
240
+        } else {
241
+            $className = get_class($object);
242
+        }
243
+        $reflection = new \ReflectionClass($className);
244
+
245
+        if ($reflection->hasMethod($methodName)) {
246
+            $method = $reflection->getMethod($methodName);
247
+
248
+            $method->setAccessible(true);
249
+
250
+            return $method->invokeArgs($object, $parameters);
251
+        } elseif ($reflection->hasProperty($methodName)) {
252
+            $property = $reflection->getProperty($methodName);
253
+
254
+            $property->setAccessible(true);
255
+
256
+            if (!empty($parameters)) {
257
+                if ($property->isStatic()) {
258
+                    $property->setValue(null, array_pop($parameters));
259
+                } else {
260
+                    $property->setValue($object, array_pop($parameters));
261
+                }
262
+            }
263
+
264
+            if (is_object($object)) {
265
+                return $property->getValue($object);
266
+            }
267
+
268
+            return $property->getValue();
269
+        } elseif ($reflection->hasConstant($methodName)) {
270
+            return $reflection->getConstant($methodName);
271
+        }
272
+
273
+        return false;
274
+    }
275
+
276
+    /**
277
+     * Returns a unique identifier as uniqid() is not reliable sometimes
278
+     *
279
+     * @param string $prefix
280
+     * @param int $length
281
+     * @return string
282
+     */
283
+    protected static function getUniqueID($prefix = '', $length = 13) {
284
+        return $prefix . Server::get(ISecureRandom::class)->generate(
285
+            $length,
286
+            // Do not use dots and slashes as we use the value for file names
287
+            ISecureRandom::CHAR_DIGITS . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_UPPER
288
+        );
289
+    }
290
+
291
+    /**
292
+     * Filter methods
293
+     *
294
+     * Returns all methods of the given class,
295
+     * that are public or abstract and not in the ignoreMethods list,
296
+     * to be able to fill onlyMethods() with an inverted list.
297
+     *
298
+     * @param string $className
299
+     * @param string[] $filterMethods
300
+     * @return string[]
301
+     */
302
+    public function filterClassMethods(string $className, array $filterMethods): array {
303
+        $class = new \ReflectionClass($className);
304
+
305
+        $methods = [];
306
+        foreach ($class->getMethods() as $method) {
307
+            if (($method->isPublic() || $method->isAbstract()) && !in_array($method->getName(), $filterMethods, true)) {
308
+                $methods[] = $method->getName();
309
+            }
310
+        }
311
+
312
+        return $methods;
313
+    }
314
+
315
+    public static function tearDownAfterClass(): void {
316
+        if (!self::$wasDatabaseAllowed && self::$realDatabase !== null) {
317
+            // in case an error is thrown in a test, PHPUnit jumps straight to tearDownAfterClass,
318
+            // so we need the database again
319
+            \OC::$server->registerService(IDBConnection::class, function () {
320
+                return self::$realDatabase;
321
+            });
322
+        }
323
+        $dataDir = Server::get(IConfig::class)->getSystemValueString('datadirectory', \OC::$SERVERROOT . '/data-autotest');
324
+        if (self::$wasDatabaseAllowed && Server::get(IDBConnection::class)) {
325
+            $db = Server::get(IDBConnection::class);
326
+            if ($db->inTransaction()) {
327
+                $db->rollBack();
328
+                throw new \Exception('There was a transaction still in progress and needed to be rolled back. Please fix this in your test.');
329
+            }
330
+            $queryBuilder = $db->getQueryBuilder();
331
+
332
+            self::tearDownAfterClassCleanShares($queryBuilder);
333
+            self::tearDownAfterClassCleanStorages($queryBuilder);
334
+            self::tearDownAfterClassCleanFileCache($queryBuilder);
335
+        }
336
+        self::tearDownAfterClassCleanStrayDataFiles($dataDir);
337
+        self::tearDownAfterClassCleanStrayHooks();
338
+        self::tearDownAfterClassCleanStrayLocks();
339
+
340
+        // Ensure we start with fresh instances of some classes to reduce side-effects between tests
341
+        unset(\OC::$server[\OC\Files\AppData\Factory::class]);
342
+        unset(\OC::$server[\OC\App\AppStore\Fetcher\AppFetcher::class]);
343
+        unset(\OC::$server[\OC\Installer::class]);
344
+        unset(\OC::$server[\OC\Updater::class]);
345
+
346
+        /** @var SetupManager $setupManager */
347
+        $setupManager = Server::get(SetupManager::class);
348
+        $setupManager->tearDown();
349
+
350
+        /** @var MountProviderCollection $mountProviderCollection */
351
+        $mountProviderCollection = Server::get(MountProviderCollection::class);
352
+        $mountProviderCollection->clearProviders();
353
+
354
+        /** @var IConfig $config */
355
+        $config = Server::get(IConfig::class);
356
+        $mountProviderCollection->registerProvider(new CacheMountProvider($config));
357
+        $mountProviderCollection->registerHomeProvider(new LocalHomeMountProvider());
358
+        $objectStoreConfig = Server::get(PrimaryObjectStoreConfig::class);
359
+        $mountProviderCollection->registerRootProvider(new RootMountProvider($objectStoreConfig, $config));
360
+
361
+        $setupManager->setupRoot();
362
+
363
+        parent::tearDownAfterClass();
364
+    }
365
+
366
+    /**
367
+     * Remove all entries from the share table
368
+     *
369
+     * @param IQueryBuilder $queryBuilder
370
+     */
371
+    protected static function tearDownAfterClassCleanShares(IQueryBuilder $queryBuilder) {
372
+        $queryBuilder->delete('share')
373
+            ->executeStatement();
374
+    }
375
+
376
+    /**
377
+     * Remove all entries from the storages table
378
+     *
379
+     * @param IQueryBuilder $queryBuilder
380
+     */
381
+    protected static function tearDownAfterClassCleanStorages(IQueryBuilder $queryBuilder) {
382
+        $queryBuilder->delete('storages')
383
+            ->executeStatement();
384
+    }
385
+
386
+    /**
387
+     * Remove all entries from the filecache table
388
+     *
389
+     * @param IQueryBuilder $queryBuilder
390
+     */
391
+    protected static function tearDownAfterClassCleanFileCache(IQueryBuilder $queryBuilder) {
392
+        $queryBuilder->delete('filecache')
393
+            ->runAcrossAllShards()
394
+            ->executeStatement();
395
+    }
396
+
397
+    /**
398
+     * Remove all unused files from the data dir
399
+     *
400
+     * @param string $dataDir
401
+     */
402
+    protected static function tearDownAfterClassCleanStrayDataFiles($dataDir) {
403
+        $knownEntries = [
404
+            'nextcloud.log' => true,
405
+            'audit.log' => true,
406
+            'owncloud.db' => true,
407
+            '.ocdata' => true,
408
+            '..' => true,
409
+            '.' => true,
410
+        ];
411
+
412
+        if ($dh = opendir($dataDir)) {
413
+            while (($file = readdir($dh)) !== false) {
414
+                if (!isset($knownEntries[$file])) {
415
+                    self::tearDownAfterClassCleanStrayDataUnlinkDir($dataDir . '/' . $file);
416
+                }
417
+            }
418
+            closedir($dh);
419
+        }
420
+    }
421
+
422
+    /**
423
+     * Recursive delete files and folders from a given directory
424
+     *
425
+     * @param string $dir
426
+     */
427
+    protected static function tearDownAfterClassCleanStrayDataUnlinkDir($dir) {
428
+        if ($dh = @opendir($dir)) {
429
+            while (($file = readdir($dh)) !== false) {
430
+                if (Filesystem::isIgnoredDir($file)) {
431
+                    continue;
432
+                }
433
+                $path = $dir . '/' . $file;
434
+                if (is_dir($path)) {
435
+                    self::tearDownAfterClassCleanStrayDataUnlinkDir($path);
436
+                } else {
437
+                    @unlink($path);
438
+                }
439
+            }
440
+            closedir($dh);
441
+        }
442
+        @rmdir($dir);
443
+    }
444
+
445
+    /**
446
+     * Clean up the list of hooks
447
+     */
448
+    protected static function tearDownAfterClassCleanStrayHooks() {
449
+        \OC_Hook::clear();
450
+    }
451
+
452
+    /**
453
+     * Clean up the list of locks
454
+     */
455
+    protected static function tearDownAfterClassCleanStrayLocks() {
456
+        Server::get(ILockingProvider::class)->releaseAll();
457
+    }
458
+
459
+    /**
460
+     * Login and setup FS as a given user,
461
+     * sets the given user as the current user.
462
+     *
463
+     * @param string $user user id or empty for a generic FS
464
+     */
465
+    protected static function loginAsUser($user = '') {
466
+        self::logout();
467
+        Filesystem::tearDown();
468
+        \OC_User::setUserId($user);
469
+        $userObject = Server::get(IUserManager::class)->get($user);
470
+        if (!is_null($userObject)) {
471
+            $userObject->updateLastLoginTimestamp();
472
+        }
473
+        \OC_Util::setupFS($user);
474
+        if (Server::get(IUserManager::class)->userExists($user)) {
475
+            \OC::$server->getUserFolder($user);
476
+        }
477
+    }
478
+
479
+    /**
480
+     * Logout the current user and tear down the filesystem.
481
+     */
482
+    protected static function logout() {
483
+        \OC_Util::tearDownFS();
484
+        \OC_User::setUserId('');
485
+        // needed for fully logout
486
+        Server::get(IUserSession::class)->setUser(null);
487
+    }
488
+
489
+    /**
490
+     * Run all commands pushed to the bus
491
+     */
492
+    protected function runCommands() {
493
+        // get the user for which the fs is setup
494
+        $view = Filesystem::getView();
495
+        if ($view) {
496
+            [, $user] = explode('/', $view->getRoot());
497
+        } else {
498
+            $user = null;
499
+        }
500
+
501
+        \OC_Util::tearDownFS(); // command can't reply on the fs being setup
502
+        $this->commandBus->run();
503
+        \OC_Util::tearDownFS();
504
+
505
+        if ($user) {
506
+            \OC_Util::setupFS($user);
507
+        }
508
+    }
509
+
510
+    /**
511
+     * Check if the given path is locked with a given type
512
+     *
513
+     * @param View $view view
514
+     * @param string $path path to check
515
+     * @param int $type lock type
516
+     * @param bool $onMountPoint true to check the mount point instead of the
517
+     *                           mounted storage
518
+     *
519
+     * @return boolean true if the file is locked with the
520
+     *                 given type, false otherwise
521
+     */
522
+    protected function isFileLocked($view, $path, $type, $onMountPoint = false) {
523
+        // Note: this seems convoluted but is necessary because
524
+        // the format of the lock key depends on the storage implementation
525
+        // (in our case mostly md5)
526
+
527
+        if ($type === ILockingProvider::LOCK_SHARED) {
528
+            // to check if the file has a shared lock, try acquiring an exclusive lock
529
+            $checkType = ILockingProvider::LOCK_EXCLUSIVE;
530
+        } else {
531
+            // a shared lock cannot be set if exclusive lock is in place
532
+            $checkType = ILockingProvider::LOCK_SHARED;
533
+        }
534
+        try {
535
+            $view->lockFile($path, $checkType, $onMountPoint);
536
+            // no exception, which means the lock of $type is not set
537
+            // clean up
538
+            $view->unlockFile($path, $checkType, $onMountPoint);
539
+            return false;
540
+        } catch (LockedException $e) {
541
+            // we could not acquire the counter-lock, which means
542
+            // the lock of $type was in place
543
+            return true;
544
+        }
545
+    }
546
+
547
+    protected function getGroupAnnotations(): array {
548
+        if (method_exists($this, 'getAnnotations')) {
549
+            $annotations = $this->getAnnotations();
550
+            return $annotations['class']['group'] ?? [];
551
+        }
552
+
553
+        $r = new \ReflectionClass($this);
554
+        $doc = $r->getDocComment();
555
+
556
+        if (class_exists(Group::class)) {
557
+            $attributes = array_map(function (\ReflectionAttribute $attribute) {
558
+                /** @var Group $group */
559
+                $group = $attribute->newInstance();
560
+                return $group->name();
561
+            }, $r->getAttributes(Group::class));
562
+            if (count($attributes) > 0) {
563
+                return $attributes;
564
+            }
565
+        }
566
+        preg_match_all('#@group\s+(.*?)\n#s', $doc, $annotations);
567
+        return $annotations[1] ?? [];
568
+    }
569
+
570
+    protected function IsDatabaseAccessAllowed(): bool {
571
+        $annotations = $this->getGroupAnnotations();
572
+        if (isset($annotations)) {
573
+            if (in_array('DB', $annotations) || in_array('SLOWDB', $annotations)) {
574
+                return true;
575
+            }
576
+        }
577
+
578
+        return false;
579
+    }
580
+
581
+    /**
582
+     * @param string $expectedHtml
583
+     * @param string $template
584
+     * @param array $vars
585
+     */
586
+    protected function assertTemplate($expectedHtml, $template, $vars = []) {
587
+        $requestToken = 12345;
588
+        /** @var Defaults|\PHPUnit\Framework\MockObject\MockObject $l10n */
589
+        $theme = $this->getMockBuilder('\OCP\Defaults')
590
+            ->disableOriginalConstructor()->getMock();
591
+        $theme->expects($this->any())
592
+            ->method('getName')
593
+            ->willReturn('Nextcloud');
594
+        /** @var IL10N|\PHPUnit\Framework\MockObject\MockObject $l10n */
595
+        $l10n = $this->getMockBuilder(IL10N::class)
596
+            ->disableOriginalConstructor()->getMock();
597
+        $l10n
598
+            ->expects($this->any())
599
+            ->method('t')
600
+            ->willReturnCallback(function ($text, $parameters = []) {
601
+                return vsprintf($text, $parameters);
602
+            });
603
+
604
+        $t = new Base($template, $requestToken, $l10n, $theme);
605
+        $buf = $t->fetchPage($vars);
606
+        $this->assertHtmlStringEqualsHtmlString($expectedHtml, $buf);
607
+    }
608
+
609
+    /**
610
+     * @param string $expectedHtml
611
+     * @param string $actualHtml
612
+     * @param string $message
613
+     */
614
+    protected function assertHtmlStringEqualsHtmlString($expectedHtml, $actualHtml, $message = '') {
615
+        $expected = new DOMDocument();
616
+        $expected->preserveWhiteSpace = false;
617
+        $expected->formatOutput = true;
618
+        $expected->loadHTML($expectedHtml);
619
+
620
+        $actual = new DOMDocument();
621
+        $actual->preserveWhiteSpace = false;
622
+        $actual->formatOutput = true;
623
+        $actual->loadHTML($actualHtml);
624
+        $this->removeWhitespaces($actual);
625
+
626
+        $expectedHtml1 = $expected->saveHTML();
627
+        $actualHtml1 = $actual->saveHTML();
628
+        self::assertEquals($expectedHtml1, $actualHtml1, $message);
629
+    }
630
+
631
+
632
+    private function removeWhitespaces(DOMNode $domNode) {
633
+        foreach ($domNode->childNodes as $node) {
634
+            if ($node->hasChildNodes()) {
635
+                $this->removeWhitespaces($node);
636
+            } else {
637
+                if ($node instanceof \DOMText && $node->isWhitespaceInElementContent()) {
638
+                    $domNode->removeChild($node);
639
+                }
640
+            }
641
+        }
642
+    }
643 643
 }
Please login to merge, or discard this patch.