62766e1e71
A database dump that fills a whole pack (32 MiB) uploads that pack while the dump transaction is still open, and the pack's index cache row was inserted on the same connection. PostgreSQL refuses that (25006, read-only transaction) and the run failed; on other databases the row was rolled back with the dump transaction. The index now holds those cache writes back until the transaction is over.
92 lines
3.5 KiB
PHP
92 lines
3.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace OCA\NgBackup\Tests\Unit\Repository;
|
|
|
|
use OCA\NgBackup\Backend\LocalBackend;
|
|
use OCA\NgBackup\Crypto\KeyRing;
|
|
use OCA\NgBackup\Repository\IIndexCache;
|
|
use OCA\NgBackup\Repository\Repository;
|
|
use OCA\NgBackup\Tests\Unit\TempDirTrait;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class IndexCacheTest extends TestCase {
|
|
use TempDirTrait;
|
|
|
|
public function testSecondOpenDownloadsNoIndexFiles(): void {
|
|
$cache = new class implements IIndexCache {
|
|
public array $data = [];
|
|
public function all(string $r): array { return $this->data[$r] ?? []; }
|
|
public function put(string $r, string $p, array $e): void { $this->data[$r][$p] = $e; }
|
|
public function remove(string $r, array $ps): void { foreach ($ps as $p) { unset($this->data[$r][$p]); } }
|
|
};
|
|
$src = $this->tempDir();
|
|
for ($i = 0; $i < 12; $i++) {
|
|
file_put_contents("$src/f$i", random_bytes(3 * 1048576));
|
|
}
|
|
$backend = new LocalBackend($this->tempDir());
|
|
$keys = KeyRing::generate();
|
|
Repository::initWithKey($backend, $keys, $keys->wrap('pw'));
|
|
|
|
$repo = Repository::openWithKey($backend, $keys, $cache);
|
|
$r1 = $repo->backupDirectory($src);
|
|
$this->assertGreaterThan(0, count($backend->list('index')));
|
|
|
|
$again = Repository::openWithKey($backend, $keys, $cache);
|
|
$this->assertSame(0, $again->indexDownloads(), 'everything came from the cache');
|
|
|
|
file_put_contents("$src/new", random_bytes(2 * 1048576));
|
|
$again->backupDirectory($src, $r1['snapshot']);
|
|
$third = Repository::openWithKey($backend, $keys, $cache);
|
|
$this->assertSame(0, $third->indexDownloads(), 'packs written by this server are cached on write');
|
|
|
|
// A cache that misses entries (e.g. another server wrote packs) fetches only those.
|
|
$repoId = array_key_first($cache->data);
|
|
array_pop($cache->data[$repoId]);
|
|
$fourth = Repository::openWithKey($backend, $keys, $cache);
|
|
$this->assertSame(1, $fourth->indexDownloads());
|
|
|
|
$target = $this->tempDir();
|
|
$fourth->restore($r1['snapshot'], $target);
|
|
$this->assertCount(12, self::hashTree($target));
|
|
}
|
|
|
|
/** The database dump reads in a read-only transaction: a pack that fills up then must not write the cache yet. */
|
|
public function testHeldCacheWritesWaitForRelease(): void {
|
|
$cache = new class implements IIndexCache {
|
|
public array $data = [];
|
|
public bool $readOnly = false;
|
|
public function all(string $r): array { return $this->data[$r] ?? []; }
|
|
public function put(string $r, string $p, array $e): void {
|
|
if ($this->readOnly) {
|
|
throw new \RuntimeException('cannot execute INSERT in a read-only transaction');
|
|
}
|
|
$this->data[$r][$p] = $e;
|
|
}
|
|
public function remove(string $r, array $ps): void { foreach ($ps as $p) { unset($this->data[$r][$p]); } }
|
|
};
|
|
$backend = new LocalBackend($this->tempDir());
|
|
$keys = KeyRing::generate();
|
|
Repository::initWithKey($backend, $keys, $keys->wrap('pw'));
|
|
$repo = Repository::openWithKey($backend, $keys, $cache);
|
|
|
|
$stats = [];
|
|
$repo->holdIndexCache();
|
|
$cache->readOnly = true;
|
|
$writer = $repo->blobWriter($stats);
|
|
for ($i = 0; $i < 40; $i++) {
|
|
$writer->write(random_bytes(1048576)); // more than one 32 MiB pack
|
|
}
|
|
$writer->finish();
|
|
$this->assertGreaterThan(0, count($backend->list('index')), 'a full pack was uploaded during the hold');
|
|
$this->assertSame([], $cache->data);
|
|
|
|
$cache->readOnly = false;
|
|
$repo->releaseIndexCache();
|
|
$repo->flushPacks();
|
|
$this->assertCount(count($backend->list('index')), array_merge(...array_values($cache->data)));
|
|
$this->assertSame(0, Repository::openWithKey($backend, $keys, $cache)->indexDownloads());
|
|
}
|
|
}
|