Quality of life things.
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Build and test / requirements (push) Failing after 3m48s

This commit is contained in:
Jens True 2024-02-07 11:00:08 +00:00
parent aed385f5e0
commit 6ce774236e
18 changed files with 226 additions and 1885 deletions

3
.gitignore vendored

@ -6,4 +6,5 @@
config.yml
*.phar
*.deb
.phpunit.result.cache
.phpunit.result.cache
*cache/

51
.phpcs.xml Normal file

@ -0,0 +1,51 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer" xsi:noNamespaceSchemaLocation="phpcs.xsd">
<description>Coding standard</description>
<file>src/</file>
<file>tests/</file>
<rule ref="PSR1">
<exclude name="Generic.Files.LineLength"/>
</rule>
<rule ref="PSR2"></rule>
<rule ref="PSR12"></rule>
<rule ref="Generic">
<exclude name="Generic.WhiteSpace.DisallowSpaceIndent.SpacesUsed"/>
<exclude name="Generic.Files.LowercasedFilename.NotFound"/>
<exclude name="Generic.PHP.ClosingPHPTag.NotFound"/>
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
<exclude name="Generic.Files.EndFileNoNewline.Found"/>
<exclude name="Generic.Arrays.DisallowShortArraySyntax.Found"/>
<exclude name="Generic.Functions.OpeningFunctionBraceKernighanRitchie.BraceOnNewLine"/>
<exclude name="Generic.Classes.OpeningBraceSameLine.BraceOnNewLine"/>
<exclude name="Generic.PHP.LowerCaseConstant.Found"/>
<exclude name="Generic.Formatting.SpaceAfterCast"/>
<exclude name="Generic.Formatting.MultipleStatementAlignment.NotSameWarning"/>
<exclude name="Generic.Commenting.DocComment.MissingShort"/>
<exclude name="Generic.NamingConventions.AbstractClassNamePrefix.Missing"/>
<exclude name="Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed"/>
<exclude name="Generic.NamingConventions.InterfaceNameSuffix.Missing"/>
<exclude name="Generic.Commenting.Todo.TaskFound"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUse"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassAfterLastUsed"/>
<exclude name="Generic.CodeAnalysis.UnusedFunctionParameter.FoundInImplementedInterfaceAfterLastUsed"/>
<exclude name="Generic.Formatting.SpaceBeforeCast.NoSpace"/>
<exclude name="Generic.CodeAnalysis.UselessOverridingMethod.Found"/>
<exclude name="Squiz.Functions.MultiLineFunctionDeclaration.NewlineBeforeOpenBrace"/>
</rule>
<!-- Ban some functions -->
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="sizeof" value="count"/>
<element key="delete" value="unset"/>
<element key="print" value="echo"/>
<element key="is_null" value="null"/>
<element key="create_function" value="null"/>
</property>
</properties>
</rule>
</ruleset>

@ -23,10 +23,6 @@
}
},
"require-dev": {
"squizlabs/php_codesniffer": "*",
"phpstan/phpstan": "^1.10.37",
"vimeo/psalm": "^5.15",
"phpmd/phpmd": "^2.14.1",
"phpunit/phpunit": "^10.3.5",
"phpmetrics/phpmetrics": "^2.8.2"
},
@ -34,7 +30,7 @@
"test": "vendor/bin/phpunit tests --display-warnings",
"test-full": "vendor/bin/phpunit -c phpunit.full.xml",
"metrics": "vendor/bin/phpmetrics --report-html=output/metrics --junit=output/test.xml src/",
"docs": "./phpDocumentor.phar --setting=graphs.enabled=true",
"docs": "phpDocumentor --setting=graphs.enabled=true",
"analyze": [
"@analyze-yaml",
"@analyze-phpmd",
@ -43,9 +39,9 @@
"@analyze-phpcs"
],
"analyze-yaml": "vendor/bin/yaml-lint *.yml .*.yml *.json",
"analyze-phpmd": "vendor/bin/phpmd src,tests text cleancode,codesize,controversial,design,naming,unusedcode",
"analyze-phpstan":"vendor/bin/phpstan analyze --level=8 --error-format=raw src/ backup tests",
"analyze-psalm": "vendor/bin/psalm --no-cache",
"analyze-phpcs": "vendor/bin/phpcs src backup tests --report=emacs --standard=PSR12"
"analyze-phpmd": "phpmd src,tests text cleancode,codesize,controversial,design,naming,unusedcode",
"analyze-phpstan":"phpstan",
"analyze-psalm": "psalm --no-cache",
"analyze-phpcs": "phpcs"
}
}

1871
composer.lock generated

File diff suppressed because it is too large Load Diff

5
phpstan.neon Normal file

@ -0,0 +1,5 @@
parameters:
level: 8
paths:
- src
- tests

@ -1,36 +0,0 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
executionOrder="random"
testdox="true"
displayDetailsOnIncompleteTests="true"
displayDetailsOnSkippedTests="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
>
<testsuites>
<testsuite name="All">
<directory>tests</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src</directory>
<file>backup</file>
</include>
</source>
<logging>
<junit outputFile="output/test.xml"/>
<testdoxHtml outputFile="output/test.html"/>
<testdoxText outputFile="output/test.txt"/>
</logging>
<coverage includeUncoveredFiles="true"
pathCoverage="true">
<report>
<html outputDirectory="output/coverage"/>
<text outputFile="output/coverage.txt" showUncoveredFiles="true" showOnlySummary="true"/>
</report>
</coverage>
</phpunit>

@ -1,11 +1,24 @@
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/6.3/phpunit.xsd"
>
<testsuites>
<testsuite name="All">
<directory>tests</directory>
</testsuite>
</testsuites>
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false" displayDetailsOnTestsThatTriggerWarnings="true">
<testsuites>
<testsuite name="Backupscript Test Suite">
<directory>./tests/</directory>
</testsuite>
</testsuites>
<source>
<include>
<directory suffix=".php">src/</directory>
</include>
</source>
<logging>
<junit outputFile="output/test/junit.xml"/>
<testdoxHtml outputFile="output/test/index.html"/>
</logging>
<coverage>
<report>
<html outputDirectory="output/coverage" />
<clover outputFile ="output/coverage/clover.xml" />
</report>
</coverage>
</phpunit>

@ -6,7 +6,7 @@
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
findUnusedBaselineEntry="true"
findUnusedCode="true"
findUnusedCode="false"
strictBinaryOperands="true"
checkForThrowsInGlobalScope="true"
ignoreInternalFunctionFalseReturn="false"
@ -18,5 +18,9 @@
<projectFiles>
<directory name="src/" />
<file name="backup" />
<directory name="tests" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
</psalm>

@ -26,6 +26,7 @@ class App
* Create a new instance providing a config file.
*
* @param string $configFile Relative or full path to YML config.
*
* @SuppressWarnings(PHPMD.StaticAccess)
*/
public function __construct(string $configFile)
@ -60,7 +61,9 @@ class App
]);
$parser = new Yaml();
/** @var array<string, mixed> */
/**
* @var array<string, mixed>
*/
$parsedConfig = $parser->parseFile($configFile);
// Merge those values into the configuration schema:
@ -80,11 +83,14 @@ class App
* Get configuration from key
*
* @param non-empty-string $key Key to fetch
*
* @return mixed Configuration value
*/
public function getConfig(string $key): mixed
{
/** @var string|array<string, string> */
/**
* @var string|array<string, string>
*/
$ret = $this->config->get($key);
$this->logger->debug("Fetching configuration key", [$key, $ret]);
return $ret;

@ -65,19 +65,25 @@ class CommandBackup extends Command
$rclone = new Rclone($app->getLogger()->withName('rclone'), (string)$app->getConfig('rclone.path'));
$notification = new Notification();
/** @var array<array-key,array<string,string>> */
/**
* @var array<array-key,array<string,string>>
*/
$notificationConfig = $app->getConfig('notification');
$notification->loadMany($notificationConfig);
/** @var array<string,string> */
/**
* @var array<string,string>
*/
$templateConfig = $app->getConfig('templates');
$render = new Twig($templateConfig);
/** @var array{title: string, source: string, destination: string}[] */
/**
* @var array{title: string, source: string, destination: string}[]
*/
$backupElements = $app->getConfig('backup');
foreach ($sioProgressbar->progressIterate($backupElements) as $conf) {
$title = $conf['title'];
$template = array();
$template = [];
$template['config'] = $conf;
try {
$template['start'] = new DateTime();
@ -85,7 +91,9 @@ class CommandBackup extends Command
$template['rclone_version'] = $rclone->getVersion();
$template['destination_size_before'] = $rclone->getSize($conf['destination']);
/** @var array<array-key, string> */
/**
* @var array<array-key, string>
*/
$rcloneOptions = $app->getConfig('rclone.options');
$rclone->copy($conf['source'], $conf['destination'], $rcloneOptions);

@ -42,7 +42,9 @@ class CommandShow extends Command
$sio->error('Configuration error: ' . $e->getMessage());
return Command::FAILURE;
}
/** @var array{title: string, source: string, destination: string}[] */
/**
* @var array{title: string, source: string, destination: string}[]
*/
$backupElements = $app->getConfig('backup');
$sio->table(
['Description', 'Source', 'Destination'],

@ -12,7 +12,7 @@ class Notification
/**
* @var NotificationInterface[] $notifiers
*/
private array $notifiers = array();
private array $notifiers = [];
public function __construct(private NullLogger $logger = new NullLogger())
{
@ -35,6 +35,7 @@ class Notification
*
* @param string $key Notification class
* @param string[] $config Implementation specific configuration
*
* @SuppressWarnings(PHPMD)
*/
public function loadSingle(string $key, array $config): void

@ -35,9 +35,6 @@ class Ntfy implements NotificationInterface
return $instance;
}
/**
* @todo The constructor should be private but static code analysis complains.
*/
public function __construct(private Client $client)
{
}
@ -49,7 +46,7 @@ class Ntfy implements NotificationInterface
*/
public function setTopic(string $topic): void
{
if (!strlen($topic) || strlen($topic) > self::TOPIC_MAX_LENGTH) {
if (! strlen($topic) || strlen($topic) > self::TOPIC_MAX_LENGTH) {
throw new InvalidArgumentException("Invalid topic length");
}
@ -69,11 +66,11 @@ class Ntfy implements NotificationInterface
*/
public function send(string $title, string $message): void
{
if (!strlen($title) || strlen($title) > self::TITLE_MAX_LENGTH) {
if (! strlen($title) || strlen($title) > self::TITLE_MAX_LENGTH) {
throw new InvalidArgumentException("Invalid title length");
}
if (!strlen($message) || strlen($message) > self::MESSAGE_MAX_LENGTH) {
if (! strlen($message) || strlen($message) > self::MESSAGE_MAX_LENGTH) {
throw new InvalidArgumentException("Invalid message length");
}

@ -34,13 +34,13 @@ class Rclone
$this->rclonePath = $rclonePath;
$process = $this->exec('--version');
if (!$process->isSuccessful()) {
if (! $process->isSuccessful()) {
throw new Exception("Check installation of rclone");
}
$this->version = explode("\n", $process->getOutput())[0];
if (!\str_contains($this->version, 'rclone')) {
if (! \str_contains($this->version, 'rclone')) {
throw new Exception("rclone not recognized");
}
}
@ -66,12 +66,14 @@ class Rclone
{
$process = $this->exec('size', ['--json', $path]);
if (!$process->isSuccessful()) {
if (! $process->isSuccessful()) {
throw new Exception($process->getErrorOutput());
}
/** @var array{bytes: int} */
$output = json_decode($process->getOutput(), true);
/**
* @var array{bytes: int}
*/
$output = json_decode($process->getOutput(), TRUE);
return $output['bytes'];
}
@ -82,9 +84,9 @@ class Rclone
* @param $dest Destination mount and path
* @param string[] $additionalOptions Additional options
*/
public function copy(string $src, string $dest, array $additionalOptions = array()): void
public function copy(string $src, string $dest, array $additionalOptions = []): void
{
$options = array();
$options = [];
$options[] = $src;
$options[] = $dest;
@ -95,7 +97,7 @@ class Rclone
}
$process = $this->exec('copy', $options);
if (!$process->isSuccessful()) {
if (! $process->isSuccessful()) {
throw new Exception($process->getErrorOutput());
}
}
@ -108,7 +110,7 @@ class Rclone
*
* @return Process Instance.
*/
private function exec(string $command, array $options = array()): Process
private function exec(string $command, array $options = []): Process
{
$process = new Process(
array_merge(
@ -124,7 +126,7 @@ class Rclone
$process->run();
// executes after the command finishes
if (!$process->isSuccessful()) {
if (! $process->isSuccessful()) {
$this->logger->error("Failed execution");
}
$this->logger->info("Return code", [$process->getExitCode()]);

@ -22,9 +22,9 @@ class TwigExtension extends AbstractExtension
*/
public function getFilters(): array
{
return array(
new TwigFilter('formatBytes', array($this, 'formatBytes')),
);
return [
new TwigFilter('formatBytes', [$this, 'formatBytes']),
];
}
/**

@ -15,10 +15,10 @@ final class CommandBackupTest extends \PHPUnit\Framework\TestCase
{
protected function setUp(): void
{
if (!is_dir('temp')) {
if (! is_dir('temp')) {
mkdir('temp');
}
if (!is_dir('temp/destination')) {
if (! is_dir('temp/destination')) {
mkdir('temp/destination');
}
exec('rclone test makefiles --files 10 temp/source 2>&1');

@ -12,7 +12,9 @@ use Exception;
final class NotificationTest extends TestCase
{
/** @var array<string, string> */
/**
* @var array<string, string>
*/
private static array $config = ['type' => 'ntfy', 'domain' => 'https://test.com', 'topic' => 'testing'];
public function testloadSingle(): void

@ -16,6 +16,12 @@ final class NtfyTest extends TestCase
private Ntfy $instance;
private MockObject $client;
public function __construct($name)
{
parent::__construct($name);
$this->setUp();
}
protected function setUp(): void
{
$this->client = $this->createMock(Client::class);
@ -45,7 +51,9 @@ final class NtfyTest extends TestCase
$this->instance->send(str_repeat("t", Ntfy::TITLE_MAX_LENGTH), str_repeat("t", Ntfy::MESSAGE_MAX_LENGTH));
}
/** @return array<int, array<int, string>> */
/**
* @return array<int, array<int, string>>
*/
public static function sendBadParameterProvider(): array
{
return [
@ -75,7 +83,9 @@ final class NtfyTest extends TestCase
$this->assertEquals($topic, $this->instance->getTopic());
}
/** @return array<int, array<int, string>> */
/**
* @return array<int, array<int, string>>
*/
public static function topicBadParameterProvider(): array
{
return [