Skip to content

Commit

Permalink
Merge pull request #92 from mirko-pagliai/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
mirko-pagliai authored Jun 1, 2023
2 parents 0bdd8a6 + bd2ceb3 commit bedbd57
Show file tree
Hide file tree
Showing 11 changed files with 79 additions and 85 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,14 @@ jobs:

- name: Run psalm
if: success() || failure()
env:
db_dsn: 'sqlite:///:memory:'
run: vendor/bin/psalm.phar --output-format=github --php-version=8.0

- name: Run phpstan
if: success() || failure()
env:
db_dsn: 'sqlite:///:memory:'
run: vendor/bin/phpstan.phar analyse --error-format=github


Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# 2.x branch
## 2.11 branch
### 2.11.1
* added the `DatabaseBackup.processTimeout` configuration, which allows you to set a timeout for commands that will be
executed in sub-processes (which by default is 60 seconds) and which can be useful for exporting/importing large
databases (see [issue #88](https://github.com/mirko-pagliai/cakephp-database-backup/issues/88)). Any options to change
this timeout from `ImportCommand`/`ExportCommand` will be implemented later;
* guaranteed to work with all versions of CakePHP 4;
* added all property types to all classes;
* upgraded to the new fixture system;
* updated for `php-tools` 1.7.4;
* tests have been made compatible with Xampp on Windows;
* many, small improvements to the code and tests, also suggested by PhpStorm.
Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"require-dev": {
"cakephp/cakephp-codesniffer": "^4.4",
"cakephp/migrations": "^3.2",
"mirko-pagliai/me-tools": "^2.20.9",
"phpunit/phpunit": "^9.1|^9.5",
"phpstan/phpstan": "^1.7",
Expand Down
48 changes: 15 additions & 33 deletions config/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

use Cake\Core\Configure;
use Symfony\Component\Process\ExecutableFinder;

/**
* Executables. Name of driver as keys, Then, as value, an array that contains
Expand All @@ -35,50 +36,31 @@
define('DATABASE_BACKUP_EXTENSIONS', ['sql.bz2' => 'bzip2', 'sql.gz' => 'gzip', 'sql' => false]);
}

//Database connection
if (!Configure::check('DatabaseBackup.connection')) {
Configure::write('DatabaseBackup.connection', 'default');
}

//Auto-discovers binaries
foreach (array_unique(array_merge(array_column(DATABASE_BACKUP_EXECUTABLES, 'export'), array_column(DATABASE_BACKUP_EXECUTABLES, 'import'), ['bzip2', 'gzip'])) as $binary) {
if (!Configure::check('DatabaseBackup.binaries.' . $binary)) {
try {
$binaryPath = which($binary);
} catch (\Exception $e) {
}
Configure::write('DatabaseBackup.binaries.' . $binary, $binaryPath ?? null);
}
}

//Default chmod for backups. This works only on Unix
if (!Configure::check('DatabaseBackup.chmod')) {
Configure::write('DatabaseBackup.chmod', 0664);
}

//Default executable commands to export/import databases
foreach ([
//Writes default configuration values
$defaults = [
'DatabaseBackup.connection' => 'default',
'DatabaseBackup.chmod' => 0664,
'DatabaseBackup.target' => ROOT . 'backups',
'DatabaseBackup.mysql.export' => '{{BINARY}} --defaults-file={{AUTH_FILE}} {{DB_NAME}}',
'DatabaseBackup.mysql.import' => '{{BINARY}} --defaults-extra-file={{AUTH_FILE}} {{DB_NAME}}',
'DatabaseBackup.postgres.export' => '{{BINARY}} --format=c -b --dbname=\'postgresql://{{DB_USER}}{{DB_PASSWORD}}@{{DB_HOST}}/{{DB_NAME}}\'',
'DatabaseBackup.postgres.import' => '{{BINARY}} --format=c -c -e --dbname=\'postgresql://{{DB_USER}}{{DB_PASSWORD}}@{{DB_HOST}}/{{DB_NAME}}\'',
'DatabaseBackup.sqlite.export' => '{{BINARY}} {{DB_NAME}} .dump',
'DatabaseBackup.sqlite.import' => '{{BINARY}} {{DB_NAME}}',
] as $k => $v) {
if (!Configure::check($k)) {
Configure::write($k, $v);
}
}
];
Configure::write(array_filter($defaults, fn(string $key): bool => !Configure::check($key), ARRAY_FILTER_USE_KEY));

//Default target directory
if (!Configure::check('DatabaseBackup.target')) {
Configure::write('DatabaseBackup.target', ROOT . 'backups');
//Auto-discovers binaries
$ExecutableFinder = new ExecutableFinder();
$binaries = array_unique(array_merge(['bzip2', 'gzip'], ...array_values(array_map('array_values', DATABASE_BACKUP_EXECUTABLES))));
foreach (array_filter($binaries, fn(string $binary): bool => !Configure::check('DatabaseBackup.binaries.' . $binary)) as $binary) {
Configure::write('DatabaseBackup.binaries.' . $binary, $ExecutableFinder->find($binary));
}

//Checks for the target directory
$target = Configure::readOrFail('DatabaseBackup.target');
$target = Configure::read('DatabaseBackup.target');
if (!file_exists($target)) {
mkdir($target, 0777);
mkdir($target, 0777, true);
}
if (!is_dir($target) || !is_writeable($target)) {
trigger_error(sprintf('The directory `%s` is not writable or is not a directory', $target), E_USER_ERROR);
Expand Down
17 changes: 5 additions & 12 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,15 @@
<directory>./tests/TestCase</directory>
</testsuite>
</testsuites>


<extensions>
<extension class="\Cake\TestSuite\Fixture\PHPUnitExtension" />
</extensions>

<!-- configure code coverage -->
<filter>
<whitelist>
<directory suffix=".php">./src/</directory>
</whitelist>
</filter>

<!-- Setup a listener for fixtures -->
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector"
file="./vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager" />
</arguments>
</listener>
</listeners>
</phpunit>
1 change: 1 addition & 0 deletions src/Driver/Driver.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ final public function implementedEvents(): array
protected function _exec(string $command): Process
{
$Process = Process::fromShellCommandline($command);
$Process->setTimeout(Configure::read('DatabaseBackup.processTimeout', 60));
$Process->run();

return $Process;
Expand Down
5 changes: 1 addition & 4 deletions src/TestSuite/DriverTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,7 @@ abstract class DriverTestCase extends TestCase
/**
* @var array<string>
*/
public $fixtures = [
'core.Articles',
'core.Comments',
];
public $fixtures = ['core.Articles', 'core.Comments'];

/**
* Called before every test method
Expand Down
5 changes: 1 addition & 4 deletions tests/TestCase/BackupTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ class BackupTraitTest extends TestCase
* Fixtures
* @var array<string>
*/
public $fixtures = [
'core.Articles',
'core.Comments',
];
public $fixtures = ['core.Articles', 'core.Comments'];

/**
* Called before every test method
Expand Down
39 changes: 37 additions & 2 deletions tests/TestCase/Driver/DriverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

use DatabaseBackup\Driver\Driver;
use DatabaseBackup\TestSuite\TestCase;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;

/**
Expand Down Expand Up @@ -56,9 +57,9 @@ protected function getMockForAbstractDriver(array $mockedMethods = []): Driver
*/
protected function getMockForAbstractDriverWithErrorProcess(string $errorMessage): Driver
{
$Process = $this->createConfiguredMock(Process::class, ['getErrorOutput' => $errorMessage . PHP_EOL, 'isSuccessful' => false]);
$Driver = $this->getMockForAbstractDriver(['_exec']);
$Driver->method('_exec')
->willReturn($this->createConfiguredMock(Process::class, ['getErrorOutput' => $errorMessage . PHP_EOL, 'isSuccessful' => false]));
$Driver->method('_exec')->willReturn($Process);

return $Driver;
}
Expand Down Expand Up @@ -111,6 +112,23 @@ public function testExportOnFailure(): void
$Driver->export($this->getAbsolutePath('example.sql'));
}

/**
* Test for `export()` method, exceeding the timeout
* @see https://symfony.com/doc/current/components/process.html#process-timeout
* @test
* @uses \DatabaseBackup\Driver\Driver::_exec()
* @uses \DatabaseBackup\Driver\Driver::export()
*/
public function testExportExceedingTimeout(): void
{
$this->expectException(ProcessTimedOutException::class);
$this->expectExceptionMessage('The process "dir" exceeded the timeout of 60 seconds');
$ProcessTimedOutException = new ProcessTimedOutException(Process::fromShellCommandline('dir'), 1);
$Driver = $this->getMockForAbstractDriver(['_exec']);
$Driver->method('_exec')->willThrowException($ProcessTimedOutException);
$Driver->export($this->getAbsolutePath('example.sql'));
}

/**
* Test for `export()` method. Export is stopped because the `beforeExport()` method returns `false`
* @test
Expand All @@ -136,6 +154,23 @@ public function testImportOnFailure(): void
$Driver->import($this->getAbsolutePath('example.sql'));
}

/**
* Test for `import()` method, exceeding the timeout
* @see https://symfony.com/doc/current/components/process.html#process-timeout
* @test
* @uses \DatabaseBackup\Driver\Driver::_exec()
* @uses \DatabaseBackup\Driver\Driver::import()
*/
public function testImportExceedingTimeout(): void
{
$this->expectException(ProcessTimedOutException::class);
$this->expectExceptionMessage('The process "dir" exceeded the timeout of 60 seconds');
$ProcessTimedOutException = new ProcessTimedOutException(Process::fromShellCommandline('dir'), 1);
$Driver = $this->getMockForAbstractDriver(['_exec']);
$Driver->method('_exec')->willThrowException($ProcessTimedOutException);
$Driver->import($this->getAbsolutePath('example.sql'));
}

/**
* Test for `import()` method. Import is stopped because the `beforeImport()` method returns `false`
* @test
Expand Down
22 changes: 0 additions & 22 deletions tests/TestCase/Utility/BackupExportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@

use Cake\Core\Configure;
use Cake\TestSuite\EmailTrait;
use DatabaseBackup\Driver\Driver;
use DatabaseBackup\TestSuite\TestCase;
use DatabaseBackup\Utility\BackupExport;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Process;
use Tools\TestSuite\ReflectionTrait;

/**
Expand Down Expand Up @@ -178,23 +175,4 @@ public function testExportWithDifferentChmod(): void
$file = $this->BackupExport->filename('exportWithDifferentChmod.sql')->export();
$this->assertSame('0777', substr(sprintf('%o', fileperms($file)), -4));
}

/**
* Test for `export()` method, exceeding the timeout
* @see https://symfony.com/doc/current/components/process.html#process-timeout
* @test
* @uses \DatabaseBackup\Utility\BackupExport::export()
*/
public function testExportExceedingTimeout(): void
{
$this->expectException(ProcessTimedOutException::class);
$this->expectExceptionMessage('The process "dir" exceeded the timeout of 60 seconds');

$ProcessTimedOutException = new ProcessTimedOutException(Process::fromShellCommandline('dir'), 1);
/** @var \DatabaseBackup\Driver\Driver&\PHPUnit\Framework\MockObject\MockObject $Driver */
$Driver = $this->createPartialMockForAbstractClass(Driver::class, ['_exec'], [$this->getConnection('test')]);
$Driver->method('_exec')->willThrowException($ProcessTimedOutException);
$this->BackupExport->Driver = $Driver;
$this->BackupExport->export();
}
}
17 changes: 9 additions & 8 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Cake\Datasource\ConnectionManager;
use Cake\Mailer\Mailer;
use Cake\Mailer\TransportFactory;
use Cake\TestSuite\Fixture\SchemaLoader;
use Cake\TestSuite\TestEmailTransport;
use DatabaseBackup\Utility\BackupExport;
use DatabaseBackup\Utility\BackupManager;
Expand All @@ -28,13 +29,13 @@
ini_set('intl.default_locale', 'en_US');

define('ROOT', dirname(__DIR__) . DS);
define('CAKE_CORE_INCLUDE_PATH', ROOT . 'vendor' . DS . 'cakephp' . DS . 'cakephp');
define('CAKE_CORE_INCLUDE_PATH', ROOT . 'vendor' . DS . 'cakephp' . DS . 'cakephp' . DS);
define('CORE_PATH', ROOT . 'vendor' . DS . 'cakephp' . DS . 'cakephp' . DS);
define('CAKE', CORE_PATH . 'src' . DS);
define('TESTS', ROOT . 'tests');
define('TESTS', ROOT . 'tests' . DS);
define('APP', ROOT . 'tests' . DS . 'test_app' . DS);
define('APP_DIR', 'test_app');
define('WEBROOT_DIR', 'webroot');
define('APP_DIR', 'test_app' . DS);
define('WEBROOT_DIR', 'webroot' . DS);
define('WWW_ROOT', APP . 'webroot' . DS);
define('TMP', sys_get_temp_dir() . DS . 'cakephp-database-backup' . DS);
define('CONFIG', APP . 'config' . DS);
Expand Down Expand Up @@ -65,10 +66,6 @@
'cssBaseUrl' => 'css/',
'paths' => ['plugins' => [APP . 'Plugin' . DS]],
]);
/**
* @todo Upgrade fixtures: https://book.cakephp.org/4/en/appendices/fixture-upgrade.html
*/
Configure::write('Error.ignoredDeprecationPaths', ['tureInjector.php', '*/cakephp/src/TestSuite/Fixture/FixtureInjector.php']);

Cache::setConfig([
'_cake_core_' => [
Expand Down Expand Up @@ -108,6 +105,10 @@
Configure::write('pluginsToLoad', ['DatabaseBackup']);

require_once ROOT . 'config' . DS . 'bootstrap.php';

$loader = new SchemaLoader();
$loader->loadInternalFile(CAKE_CORE_INCLUDE_PATH . 'tests' . DS . 'schema.php');

echo 'Running tests for `' . BackupManager::getDriverName() . '` driver ' . PHP_EOL;

/**
Expand Down

0 comments on commit bedbd57

Please sign in to comment.