From b86b6f5a79912c5c15680140d08bd7309ab23372 Mon Sep 17 00:00:00 2001 From: craig410 Date: Thu, 3 Oct 2024 08:23:59 +0100 Subject: [PATCH 1/4] Support PHP 8.3 --- .github/workflows/test.yml | 3 +++ CHANGELOG.md | 4 ++++ composer.json | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 068d3b9..e2261f7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,11 +15,14 @@ jobs: matrix: php_version: - '8.2' + - '8.3' dependencies: - 'default' include: - php_version: '8.2' dependencies: 'lowest' + - php_version: '8.3' + dependencies: 'lowest' steps: - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index f151193..6e94e23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased +## v1.1.0 (2024-10-03) + +* Support PHP 8.3 + ## v1.0.0 (2024-02-06) * Initial version diff --git a/composer.json b/composer.json index 5afc785..0b01377 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "issues": "https://github.com/ingenerator/scheduled-task-runner/issues" }, "require": { - "php": "~8.2.0", + "php": "~8.2.0 || ~8.3.0", "ext-pcntl": "*", "ext-pdo": "*", "dragonmantank/cron-expression": "^3.3.2", From 913ecd9cf31ae7e9bcbd349de80865b49b7a1de4 Mon Sep 17 00:00:00 2001 From: craig410 Date: Thu, 3 Oct 2024 08:49:49 +0100 Subject: [PATCH 2/4] Upgrade tests to PHPUnit 10.5 --- composer.json | 2 +- phpunit.xml | 22 +++--- .../CronExecutionIntegrationTest.php | 7 +- .../CronjobRunnerIntegrationTest.php | 4 +- .../PDOCronExecutionHistoryTest.php | 31 ++++++-- .../PDOCronTaskStateRepositoryTest.php | 25 ++++--- test/unit/CronConfigLoaderTest.php | 25 ++++--- test/unit/CronControllerTest.php | 31 ++++---- test/unit/CronHealthcheckReporterTest.php | 50 +++++++++---- test/unit/CronStatusReporterTest.php | 30 ++++---- test/unit/CronTaskExecutionManagerTest.php | 71 +++++++++---------- test/unit/CronTaskGroupDefinitionTest.php | 20 +++--- test/unit/PDOPausedTaskListCheckerTest.php | 27 ++++--- 13 files changed, 188 insertions(+), 157 deletions(-) diff --git a/composer.json b/composer.json index 0b01377..03a1f2f 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "symfony/process": "^5.4 || ^6.0 || ^7.0" }, "require-dev": { - "phpunit/phpunit": "^9.5.5" + "phpunit/phpunit": "^10.5" }, "autoload": { "psr-4": { diff --git a/phpunit.xml b/phpunit.xml index 03c4664..1754ce2 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,18 +3,18 @@ - - - src - - + failOnDeprecation="true" + failOnNotice="true" + failOnWarning="true" + failOnRisky="true" + displayDetailsOnIncompleteTests="true" + displayDetailsOnSkippedTests="true" + displayDetailsOnTestsThatTriggerDeprecations="true" + displayDetailsOnTestsThatTriggerErrors="true" + displayDetailsOnTestsThatTriggerNotices="true" + displayDetailsOnTestsThatTriggerWarnings="true" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"> test/unit diff --git a/test/integration/CronExecutionIntegrationTest.php b/test/integration/CronExecutionIntegrationTest.php index 755a524..d0635fe 100644 --- a/test/integration/CronExecutionIntegrationTest.php +++ b/test/integration/CronExecutionIntegrationTest.php @@ -5,6 +5,7 @@ use Closure; use DateInterval; use DateTimeImmutable; +use Ingenerator\PHPUtils\DateTime\Clock\RealtimeClock; use Ingenerator\PHPUtils\Monitoring\NullMetricsAgent; use Ingenerator\ScheduledTaskRunner\CronConfigLoader; use Ingenerator\ScheduledTaskRunner\CronStatusReporter; @@ -14,12 +15,10 @@ use Ingenerator\ScheduledTaskRunner\PDOPausedTaskListChecker; use Ingenerator\ScheduledTaskRunner\SymfonyCronTaskProcessRunner; use Ingenerator\ScheduledTaskRunner\TaskExecutionState; -use Ingenerator\PHPUtils\DateTime\Clock\RealtimeClock; use PHPUnit\Framework\TestCase; use Psr\Log\AbstractLogger; use Psr\Log\NullLogger; use RuntimeException; -use test\mock\Ingenerator\ScheduledTaskRunner\Logging\SpyingLoggerStub; use function fwrite; use function is_file; use function sprintf; @@ -47,7 +46,7 @@ class CronExecutionIntegrationTest extends TestCase private $output_stream; - public function test_it_runs_expected_processes_correctly_and_identifies_completion_as_expected() + public function test_it_runs_expected_processes_correctly_and_identifies_completion_as_expected(): void { $script = $this->getTestScriptRelativePath(); @@ -286,7 +285,7 @@ private function newSubject(): CronTaskExecutionManager ); } - private function initState(string $group_name, Closure $initialiser) + private function initState(string $group_name, Closure $initialiser): void { $state = $this->state_repo->getState($group_name); $initialiser($state); diff --git a/test/integration/CronjobRunnerIntegrationTest.php b/test/integration/CronjobRunnerIntegrationTest.php index cc62608..82d0bcd 100644 --- a/test/integration/CronjobRunnerIntegrationTest.php +++ b/test/integration/CronjobRunnerIntegrationTest.php @@ -3,9 +3,9 @@ namespace test\integration\Ingenerator\ScheduledTaskRunner; use DateTimeImmutable; +use Ingenerator\PHPUtils\DateTime\Clock\RealtimeClock; use Ingenerator\ScheduledTaskRunner\CronController; use Ingenerator\ScheduledTaskRunner\PDOCronTaskStateRepository; -use Ingenerator\PHPUtils\DateTime\Clock\RealtimeClock; use PDO; use PHPUnit\Framework\ExpectationFailedException; use PHPUnit\Framework\TestCase; @@ -28,7 +28,7 @@ class CronjobRunnerIntegrationTest extends TestCase private array $tmpfiles = []; - public function test_it_can_launch_and_complete_without_errors() + public function test_it_can_launch_and_complete_without_errors(): void { // Create a temporary config and make sure the task is due to run $tmp_output = $this->getTemporaryFilename('job-file'); diff --git a/test/integration/PDOCronExecutionHistoryTest.php b/test/integration/PDOCronExecutionHistoryTest.php index 595d4f0..1040f9d 100644 --- a/test/integration/PDOCronExecutionHistoryTest.php +++ b/test/integration/PDOCronExecutionHistoryTest.php @@ -5,22 +5,39 @@ use DateTimeImmutable; use Ingenerator\ScheduledTaskRunner\PDOCronExecutionHistoryRepository; use PDO; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; class PDOCronExecutionHistoryTest extends TestCase { private PDO $pdo; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(PDOCronExecutionHistoryRepository::class, $this->newSubject()); } - /** - * @testWith [0, {"group_name": "my-group", "step_name": "whatever", "last_exit_code": 0, "last_success_at": "2021-03-02 03:02:04", "last_failure_at": null}] - * [15, {"group_name": "my-group", "step_name": "whatever", "last_exit_code": 15, "last_success_at": null, "last_failure_at": "2021-03-02 03:02:04"}] - */ - public function test_it_inserts_new_task_states_if_required($exit, $expect) + #[TestWith([ + 0, + [ + 'group_name' => 'my-group', + 'step_name' => 'whatever', + 'last_exit_code' => 0, + 'last_success_at' => '2021-03-02 03:02:04', + 'last_failure_at' => NULL, + ], + ])] + #[TestWith([ + 15, + [ + 'group_name' => 'my-group', + 'step_name' => 'whatever', + 'last_exit_code' => 15, + 'last_success_at' => NULL, + 'last_failure_at' => '2021-03-02 03:02:04', + ], + ])] + public function test_it_inserts_new_task_states_if_required($exit, $expect): void { $this->newSubject()->recordCompletion( 'my-group', @@ -32,7 +49,7 @@ public function test_it_inserts_new_task_states_if_required($exit, $expect) $this->assertSame([$expect], $this->newSubject()->listCurrentStates()); } - public function test_it_updates_correct_task_with_correct_status() + public function test_it_updates_correct_task_with_correct_status(): void { $s = $this->newSubject(); diff --git a/test/integration/PDOCronTaskStateRepositoryTest.php b/test/integration/PDOCronTaskStateRepositoryTest.php index 9ba4e43..4b2b63e 100644 --- a/test/integration/PDOCronTaskStateRepositoryTest.php +++ b/test/integration/PDOCronTaskStateRepositoryTest.php @@ -5,14 +5,15 @@ use DateInterval; use DateTimeImmutable; use DateTimeInterface; -use Ingenerator\ScheduledTaskRunner\PDOCronTaskStateRepository; -use Ingenerator\ScheduledTaskRunner\TaskExecutionState; use Ingenerator\PHPUtils\DateTime\Clock\RealtimeClock; use Ingenerator\PHPUtils\DateTime\Clock\StoppedMockClock; use Ingenerator\PHPUtils\DateTime\DateString; use Ingenerator\PHPUtils\Object\ObjectPropertyRipper; +use Ingenerator\ScheduledTaskRunner\PDOCronTaskStateRepository; +use Ingenerator\ScheduledTaskRunner\TaskExecutionState; use InvalidArgumentException; use PDO; +use PHPUnit\Framework\Attributes\TestWith; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use Psr\Log\NullLogger; @@ -28,12 +29,12 @@ class PDOCronTaskStateRepositoryTest extends BaseTestCase private LoggerInterface $log; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(PDOCronTaskStateRepository::class, $this->newSubject()); } - public function test_its_get_state_returns_existing_state_from_db() + public function test_its_get_state_returns_existing_state_from_db(): void { $this->log = $this->getDummyExpectingNoCalls(LoggerInterface::class); $this->insertDbState( @@ -63,7 +64,7 @@ public function test_its_get_state_returns_existing_state_from_db() ); } - public function test_its_get_state_never_reloads_from_db_if_task_was_not_running() + public function test_its_get_state_never_reloads_from_db_if_task_was_not_running(): void { $this->log = $this->getDummyExpectingNoCalls(LoggerInterface::class); $this->insertDbState(['group_name' => 'anything', 'is_running' => 0]); @@ -81,7 +82,7 @@ public function test_its_get_state_never_reloads_from_db_if_task_was_not_running $this->assertFalse($state2->isRunning(), 'Still not running as far as we\'re concerned'); } - public function test_its_get_state_refreshes_from_db_every_minute_if_it_was_running_when_first_loaded() + public function test_its_get_state_refreshes_from_db_every_minute_if_it_was_running_when_first_loaded(): void { $this->log = $this->getDummyExpectingNoCalls(LoggerInterface::class); $this->insertDbState(['group_name' => 'anything', 'is_running' => 1]); @@ -109,7 +110,7 @@ public function test_its_get_state_refreshes_from_db_every_minute_if_it_was_runn $this->assertNull($state3->getRefreshAt(), 'No need to refresh once it stops running'); } - public function test_its_get_state_lazily_creates_database_state_for_tasks_that_do_not_exist() + public function test_its_get_state_lazily_creates_database_state_for_tasks_that_do_not_exist(): void { $this->log = new SpyingLoggerStub(); $this->clock = StoppedMockClock::at('2022-03-01 19:38:29'); @@ -151,11 +152,9 @@ public function test_its_get_state_lazily_creates_database_state_for_tasks_that_ ); } - /** - * @testWith [true] - * [false] - */ - public function test_its_save_throws_if_state_not_in_local_collection($knows_task) + #[TestWith([TRUE])] + #[TestWith([FALSE])] + public function test_its_save_throws_if_state_not_in_local_collection($knows_task): void { // Guard against randomly passing in states that do not belong to the repo $state = TaskExecutionState::forNewTask('dunno', new DateTimeImmutable()); @@ -169,7 +168,7 @@ public function test_its_save_throws_if_state_not_in_local_collection($knows_tas $subject->save($state); } - public function test_its_save_can_update_existing_state() + public function test_its_save_can_update_existing_state(): void { $this->clock = StoppedMockClock::at('2022-01-03 20:30:02'); $subject = $this->newSubject(); diff --git a/test/unit/CronConfigLoaderTest.php b/test/unit/CronConfigLoaderTest.php index 8843010..1f7458c 100644 --- a/test/unit/CronConfigLoaderTest.php +++ b/test/unit/CronConfigLoaderTest.php @@ -6,24 +6,25 @@ use Ingenerator\ScheduledTaskRunner\CronTaskGroupDefinition; use Ingenerator\ScheduledTaskRunner\CronTaskStepDefinition; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; class CronConfigLoaderTest extends TestCase { private array $definitions = []; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(CronConfigLoader::class, $this->newSubject()); } - public function test_it_has_empty_definitions_with_no_tasks() + public function test_it_has_empty_definitions_with_no_tasks(): void { $this->definitions = []; $this->assertSame([], $this->newSubject()->getActiveTaskDefinitions()); } - public function test_it_filters_enabled_and_disabled_tasks() + public function test_it_filters_enabled_and_disabled_tasks(): void { $this->definitions = [ 'task-1' => [ @@ -49,12 +50,10 @@ public function test_it_filters_enabled_and_disabled_tasks() ); } - /** - * @testWith ["some-old-string"] - * [{"some":"junk"}] - * [{"name": "mine", "cmd":["whatever"], "extra": "foo"}] - */ - public function test_it_cannot_be_constructed_with_invalid_steps($step) + #[TestWith(['some-old-string'])] + #[TestWith([['some' => 'junk']])] + #[TestWith([['name' => 'mine', 'cmd' => ['whatever'], 'extra' => 'foo']])] + public function test_it_cannot_be_constructed_with_invalid_steps($step): void { $this->definitions = [ 'task1' => [ @@ -68,7 +67,7 @@ public function test_it_cannot_be_constructed_with_invalid_steps($step) $this->newSubject(); } - public function test_it_can_create_steps_from_array_with_valid_keys() + public function test_it_can_create_steps_from_array_with_valid_keys(): void { $this->definitions = [ 'task1' => [ @@ -91,17 +90,17 @@ public function test_it_can_create_steps_from_array_with_valid_keys() ); } - public function test_it_cannot_be_constructed_if_task_has_missing_key() + public function test_it_cannot_be_constructed_if_task_has_missing_key(): void { $this->markTestIncomplete(); } - public function test_its_tasks_have_default_values_for_optional_keys() + public function test_its_tasks_have_default_values_for_optional_keys(): void { $this->markTestIncomplete(); } - public function test_its_tasks_can_have_values_for_optional_keys() + public function test_its_tasks_can_have_values_for_optional_keys(): void { $this->markTestIncomplete(); } diff --git a/test/unit/CronControllerTest.php b/test/unit/CronControllerTest.php index 676a4b8..11508e2 100644 --- a/test/unit/CronControllerTest.php +++ b/test/unit/CronControllerTest.php @@ -35,12 +35,12 @@ class CronControllerTest extends TestCase private LoggerInterface $logger; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(CronController::class, $this->newSubject()); } - public function test_it_attempts_to_claim_the_primary_controller_lock_and_runs_tasks_if_acquired() + public function test_it_attempts_to_claim_the_primary_controller_lock_and_runs_tasks_if_acquired(): void { $lock_states = []; $subject = $this->newSubject(); @@ -65,7 +65,7 @@ function () use (&$lock_states, $subject) { ); } - public function test_it_sleeps_and_waits_to_get_the_lock_if_it_is_already_taken_on_boot() + public function test_it_sleeps_and_waits_to_get_the_lock_if_it_is_already_taken_on_boot(): void { $lock = $this->takeControllerLock(); $this->lock_check_interval_seconds = 60; @@ -101,7 +101,7 @@ function () use ($subject, &$sleeps_before_execute) { } public function test_it_returns_immediately_without_executing_if_signaled_to_quit_while_waiting_for_the_primary_lock( - ) + ): void { $this->lock_check_interval_seconds = 10; $subject = $this->newSubject(); @@ -123,7 +123,7 @@ function () use ($subject) { $this->assertSame(0, $this->execution_manager->getExecutionCount(), 'Should never have executed tasks'); } - public function test_it_returns_without_executing_anything_if_it_times_out_waiting_for_the_primary_lock() + public function test_it_returns_without_executing_anything_if_it_times_out_waiting_for_the_primary_lock(): void { $this->max_runtime = new DateInterval('PT30M'); $this->lock_check_interval_seconds = 300; // 5 minutes, so there'll be 6 sleeps before it hits half an hour @@ -136,7 +136,7 @@ public function test_it_returns_without_executing_anything_if_it_times_out_waiti $this->assertSame(0, $this->execution_manager->getExecutionCount(), 'Should never have executed tasks'); } - public function test_when_running_tasks_it_exits_if_signaled_to_quit() + public function test_when_running_tasks_it_exits_if_signaled_to_quit(): void { $subject = $this->newSubject(); @@ -161,7 +161,7 @@ function (int $exec_count) use ($subject) { ); } - public function test_when_running_tasks_it_exits_after_the_total_maximum_runtime() + public function test_when_running_tasks_it_exits_after_the_total_maximum_runtime(): void { $this->max_runtime = new DateInterval('PT6M'); $this->lock_check_interval_seconds = 300; @@ -182,7 +182,8 @@ public function test_when_running_tasks_it_exits_after_the_total_maximum_runtime ); } - public function test_it_waits_for_all_running_tasks_to_exit_before_finally_returning_even_if_it_has_been_signaled() + public function test_it_waits_for_all_running_tasks_to_exit_before_finally_returning_even_if_it_has_been_signaled( + ): void { $subject = $this->newSubject(); @@ -206,7 +207,7 @@ function () use (&$running_check_count) { ); } - public function test_once_it_has_primary_it_releases_lock_as_soon_as_it_stops_scheduling_new_tasks() + public function test_once_it_has_primary_it_releases_lock_as_soon_as_it_stops_scheduling_new_tasks(): void { $subject = $this->newSubject(); $lock_state = []; @@ -229,7 +230,7 @@ function () use (&$lock_state) { ); } - public function test_it_extends_the_primary_lock_ttl_while_running_the_task_loop() + public function test_it_extends_the_primary_lock_ttl_while_running_the_task_loop(): void { // It does, but I don't exactly know how to test this because the API for it isn't exposed in the symfony interface $this->markTestIncomplete( @@ -237,7 +238,7 @@ public function test_it_extends_the_primary_lock_ttl_while_running_the_task_loop ); } - public function test_it_still_waits_for_running_jobs_if_there_were_exceptions_during_the_task_loop() + public function test_it_still_waits_for_running_jobs_if_there_were_exceptions_during_the_task_loop(): void { $this->logger = new SpyingLoggerStub(); $e = new RuntimeException('Anything'); @@ -356,12 +357,12 @@ public function getExecutionCount(): int return $this->execution_count; } - public function onCheckRunningTasks(callable $callback) + public function onCheckRunningTasks(callable $callback): void { $this->on_check_running = $callback; } - public function onExecuteNextTasks(callable $on_execute) + public function onExecuteNextTasks(callable $on_execute): void { $this->on_execute = $on_execute; } @@ -373,12 +374,12 @@ class HookableClock extends StoppedMockClock private ?int $expect_max_sleeps = 100; - public function onSleep(callable $callback) + public function onSleep(callable $callback): void { $this->on_sleep = $callback; } - public function usleep($microseconds) + public function usleep($microseconds): void { parent::usleep($microseconds); if (($this->expect_max_sleeps !== NULL) && (count($this->sleeps) > $this->expect_max_sleeps)) { diff --git a/test/unit/CronHealthcheckReporterTest.php b/test/unit/CronHealthcheckReporterTest.php index 45f2a49..465d461 100644 --- a/test/unit/CronHealthcheckReporterTest.php +++ b/test/unit/CronHealthcheckReporterTest.php @@ -8,7 +8,9 @@ use Ingenerator\ScheduledTaskRunner\CronExecutionHistoryRepository; use Ingenerator\ScheduledTaskRunner\CronHealthcheckReporter; use Ingenerator\ScheduledTaskRunner\TestUtils\CronConfigLoaderStub; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; +use function array_merge; class CronHealthcheckReporterTest extends TestCase { @@ -18,12 +20,12 @@ class CronHealthcheckReporterTest extends TestCase private array $history_states = []; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(CronHealthcheckReporter::class, $this->newSubject()); } - public function test_it_reports_healthy_if_all_configured_crons_have_succeeded_within_expected_timeframe() + public function test_it_reports_healthy_if_all_configured_crons_have_succeeded_within_expected_timeframe(): void { $this->task_definitions = [ 'job-1' => [ @@ -53,18 +55,37 @@ public function test_it_reports_healthy_if_all_configured_crons_have_succeeded_w ); } - /** - * @testWith [{"j1-1": "2022-03-08 09:56:59"}, {"job-1--first": "2022-03-08 09:56:59"}] - * [{"j2-1": "2022-03-08 09:01:59"}, {"job-2--first": "2022-03-08 09:01:59"}] - * [{"j2-2": "2022-03-08 09:01:59"}, {"job-2--second": "2022-03-08 09:01:59"}] - * [{"j1-1": "2022-03-07 10:00:00", "j2-1":"2022-03-07 09:45:00", "j2-2": "2022-03-07 09:50:00"}, {"job-1--first":"2022-03-07 10:00:00","job-2--first":"2022-03-07 09:45:00","job-2--second":"2022-03-07 09:50:00"}] - * [{"j2-1": null}, {"job-2--first":null}] - */ + #[TestWith([ + ['j1-1' => '2022-03-08 09:56:59'], + ['job-1--first' => '2022-03-08 09:56:59'], + ])] + #[TestWith([ + ['j2-1' => '2022-03-08 09:01:59'], + ['job-2--first' => '2022-03-08 09:01:59'], + ])] + #[TestWith([ + ['j2-2' => '2022-03-08 09:01:59'], + ['job-2--second' => '2022-03-08 09:01:59'], + ])] + #[TestWith([ + [ + 'j1-1' => '2022-03-07 10:00:00', + 'j2-1' => '2022-03-07 09:45:00', + 'j2-2' => '2022-03-07 09:50:00', + ], + [ + 'job-1--first' => '2022-03-07 10:00:00', + 'job-2--first' => '2022-03-07 09:45:00', + 'job-2--second' => '2022-03-07 09:50:00', + ], + ])] + #[TestWith([['j2-1' => NULL], ['job-2--first' => NULL]])] public function test_it_reports_unhealthy_if_any_configured_cron_last_succeeded_before_configured_timeframe( $last_successes, $expect_missing - ) { - $last_successes = \array_merge( + ): void + { + $last_successes = array_merge( [ 'j1-1' => '2022-03-08 09:57:00', 'j2-1' => '2022-03-08 09:02:00', @@ -101,7 +122,7 @@ public function test_it_reports_unhealthy_if_any_configured_cron_last_succeeded_ ); } - public function test_it_reports_unhealthy_if_any_configured_cron_has_never_reported_status() + public function test_it_reports_unhealthy_if_any_configured_cron_has_never_reported_status(): void { $this->task_definitions = [ 'job-2' => [ @@ -130,7 +151,7 @@ public function test_it_reports_unhealthy_if_any_configured_cron_has_never_repor ); } - public function test_it_ignores_cron_executions_that_are_no_longer_defined_or_disabled() + public function test_it_ignores_cron_executions_that_are_no_longer_defined_or_disabled(): void { $this->task_definitions = [ 'disabled_job' => [ @@ -194,7 +215,8 @@ public function recordCompletion( string $step_name, DateTimeImmutable $end, int $exit_code - ) { + ): void + { throw new BadMethodCallException('Implement '.__METHOD__); } diff --git a/test/unit/CronStatusReporterTest.php b/test/unit/CronStatusReporterTest.php index 2339aa7..1396ddb 100644 --- a/test/unit/CronStatusReporterTest.php +++ b/test/unit/CronStatusReporterTest.php @@ -8,6 +8,7 @@ use Ingenerator\PHPUtils\Monitoring\MetricId; use Ingenerator\ScheduledTaskRunner\CronExecutionHistoryRepository; use Ingenerator\ScheduledTaskRunner\CronStatusReporter; +use PHPUnit\Framework\Attributes\TestWith; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use test\mock\Ingenerator\ScheduledTaskRunner\Logging\SpyingLoggerStub; @@ -20,22 +21,20 @@ class CronStatusReporterTest extends BaseTestCase private ArrayMetricsAgent $metrics; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(CronStatusReporter::class, $this->newSubject()); } - public function test_it_logs_starting_tasks() + public function test_it_logs_starting_tasks(): void { $this->newSubject()->reportStarting('foo', 'foo-2'); $this->logger->assertLoggedOnceMatching(LogLevel::INFO, '/^Starting task foo:foo-2$/', []); } - /** - * @testWith [0, "notice"] - * [15, "error"] - */ - public function test_it_logs_completed_tasks_with_errorlevel_if_nonzero_exit($exit_code, $expect_loglevel) + #[TestWith([0, 'notice'])] + #[TestWith([15, 'error'])] + public function test_it_logs_completed_tasks_with_errorlevel_if_nonzero_exit($exit_code, $expect_loglevel): void { $this->newSubject()->reportCompleted( 'foo', @@ -52,7 +51,7 @@ public function test_it_logs_completed_tasks_with_errorlevel_if_nonzero_exit($ex ); } - public function test_it_logs_timed_out_tasks() + public function test_it_logs_timed_out_tasks(): void { $this->newSubject()->reportTimedOut( 'my-task', @@ -67,7 +66,7 @@ public function test_it_logs_timed_out_tasks() ); } - public function test_it_does_not_report_metric_on_task_failure() + public function test_it_does_not_report_metric_on_task_failure(): void { $this->newSubject()->reportCompleted( 'any', @@ -79,11 +78,9 @@ public function test_it_does_not_report_metric_on_task_failure() AssertMetrics::assertNoMetricsCaptured($this->metrics->getMetrics()); } - /** - * @testWith ["any", "group:thing", "any--group-thing"] - * ["friends-sync", "friends-sync", "friends-sync--friends-sync"] - */ - public function test_it_reports_metric_on_task_success_with_valid_source_name($group, $step, $expect_source) + #[TestWith(['any', 'group:thing', 'any--group-thing'])] + #[TestWith(['friends-sync', 'friends-sync', 'friends-sync--friends-sync'])] + public function test_it_reports_metric_on_task_success_with_valid_source_name($group, $step, $expect_source): void { $this->newSubject()->reportCompleted( $group, @@ -101,7 +98,7 @@ public function test_it_reports_metric_on_task_success_with_valid_source_name($g ); } - public function test_it_records_last_execution_state_in_database() + public function test_it_records_last_execution_state_in_database(): void { $this->history_repo = new class() implements CronExecutionHistoryRepository { private array $states = []; @@ -111,7 +108,8 @@ public function recordCompletion( string $step_name, DateTimeImmutable $end, int $exit_code - ) { + ): void + { $this->states[] = [ 'task' => $task_group, 'step' => $step_name, diff --git a/test/unit/CronTaskExecutionManagerTest.php b/test/unit/CronTaskExecutionManagerTest.php index e5e2404..2ee3d31 100644 --- a/test/unit/CronTaskExecutionManagerTest.php +++ b/test/unit/CronTaskExecutionManagerTest.php @@ -18,6 +18,8 @@ use Ingenerator\ScheduledTaskRunner\TaskExecutionState; use Ingenerator\ScheduledTaskRunner\TestUtils\CronConfigLoaderStub; use PHPUnit\Framework\Assert; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use Symfony\Component\Process\Process; use function array_map; use function array_merge; @@ -37,12 +39,12 @@ class CronTaskExecutionManagerTest extends BaseTestCase private array $paused_task_state = []; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(CronTaskExecutionManager::class, $this->newSubject()); } - public function test_its_execute_next_does_nothing_with_no_enabled_crons() + public function test_its_execute_next_does_nothing_with_no_enabled_crons(): void { $this->task_definitions = [ 'anything' => ['is_enabled' => FALSE, 'schedule' => ['minute' => '*']], @@ -53,7 +55,7 @@ public function test_its_execute_next_does_nothing_with_no_enabled_crons() $this->process_runner->assertRanNothing(); } - public function provider_execute_tasks() + public static function provider_execute_tasks() { $single_step_task_at_five_past = [ 'steps' => ['run-something'], @@ -189,15 +191,14 @@ public function provider_execute_tasks() ]; } - /** - * @dataProvider provider_execute_tasks - */ + #[DataProvider('provider_execute_tasks')] public function test_its_execute_next_runs_single_step_task_on_schedule( $task_def, $initial_state, $at_time, $expect_run - ) { + ): void + { $this->clock = StoppedMockClock::at($at_time); $this->task_definitions = ['my-task' => $task_def]; $this->task_state_repo = ArrayCronTaskStateRepository::with( @@ -228,7 +229,7 @@ public function test_its_execute_next_runs_single_step_task_on_schedule( } public function test_its_execute_next_only_runs_steps_of_a_multi_step_group_if_the_previous_step_succeeded_otherwise_waits_to_next_schedule( - ) + ): void { $this->clock = StoppedMockClock::at('2022-03-04 02:03:05'); $this->task_definitions = [ @@ -318,11 +319,9 @@ public function test_its_execute_next_only_runs_steps_of_a_multi_step_group_if_t $this->process_runner->assertRanSteps(['second', 'first']); } - /** - * @testWith [["first"]] - * [["first", "second"]] - */ - public function test_its_execute_next_clears_timed_out_tasks_and_they_rerun_at_next_scheduled_time($steps) + #[TestWith([['first']])] + #[TestWith([['first', 'second']])] + public function test_its_execute_next_clears_timed_out_tasks_and_they_rerun_at_next_scheduled_time($steps): void { $this->clock = StoppedMockClock::at('2022-03-04 02:30:03'); $this->task_definitions = [ @@ -390,7 +389,7 @@ public function test_its_execute_next_clears_timed_out_tasks_and_they_rerun_at_n ); } - public function test_its_execute_next_operates_as_expected_with_multiple_defined_tasks() + public function test_its_execute_next_operates_as_expected_with_multiple_defined_tasks(): void { $this->clock = StoppedMockClock::at('2022-03-02 16:03:02'); $this->task_definitions = [ @@ -410,7 +409,7 @@ public function test_its_execute_next_operates_as_expected_with_multiple_defined $this->assertSame('2022-03-02 16:03:02', $t3_state->getLastRunCompletedAt()->format('Y-m-d H:i:s'), ''); } - public function test_its_execute_next_can_run_multiple_tasks_if_due() + public function test_its_execute_next_can_run_multiple_tasks_if_due(): void { $this->clock = StoppedMockClock::at('2022-03-02 16:04:02'); $this->task_definitions = [ @@ -432,13 +431,17 @@ public function test_its_execute_next_can_run_multiple_tasks_if_due() ); } - /** - * @testWith [{}, ["t1-r1", "t2-r2", "t3-r1"], "nothing paused, all run"] - * [{"t1":true}, ["t2-r2", "t3-r1"], "t1 paused so never starts"] - * [{"t2":true}, ["t1-r1", "t3-r1"], "multi-step job t2 does not run any new steps even though it is part way"] - */ - public function test_its_execute_next_does_not_start_any_step_of_a_task_that_is_paused($pause_state, $expect_ran) - { + #[TestWith([[], ['t1-r1', 't2-r2', 't3-r1'], 'nothing paused, all run'])] + #[TestWith([['t1' => TRUE], ['t2-r2', 't3-r1'], 't1 paused so never starts'])] + #[TestWith([ + ['t2' => TRUE], + ['t1-r1', 't3-r1'], + 'multi-step job t2 does not run any new steps even though it is part way', + ])] + public function test_its_execute_next_does_not_start_any_step_of_a_task_that_is_paused( + $pause_state, + $expect_ran + ): void { $this->clock = StoppedMockClock::at('2022-03-02 16:03:02'); $this->task_definitions = [ 't1' => ['schedule' => ['minute' => '*'], 'steps' => ['t1-r1']], @@ -456,11 +459,9 @@ public function test_its_execute_next_does_not_start_any_step_of_a_task_that_is_ $this->process_runner->assertRanSteps($expect_ran); } - /** - * @testWith ["executeNext"] - * ["checkRunning"] - */ - public function test_it_tracks_whether_tasks_are_still_running_and_updates_db_on_completion($check_method) + #[TestWith(['executeNext'])] + #[TestWith(['checkRunning'])] + public function test_it_tracks_whether_tasks_are_still_running_and_updates_db_on_completion($check_method): void { $this->clock = StoppedMockClock::at('2022-03-02 16:04:02'); $this->task_definitions = ['t1' => ['schedule' => ['minute' => '*'], 'steps' => ['t1-r1']]]; @@ -501,11 +502,9 @@ public function test_it_tracks_whether_tasks_are_still_running_and_updates_db_on $this->task_state_repo->assertSavedTaskTimes('t1', 2); } - /** - * @testWith [0] - * [16] - */ - public function test_it_reports_starting_and_completing_tasks($exit_code) + #[TestWith([0])] + #[TestWith([16])] + public function test_it_reports_starting_and_completing_tasks($exit_code): void { $this->process_runner->willExitCode($exit_code); $this->clock = StoppedMockClock::at('2022-03-02 16:03:02'); @@ -607,7 +606,7 @@ public function getState(string $group_name): TaskExecutionState return $this->findWith(fn(TaskExecutionState $s) => $s->getGroupName() === $group_name); } - public function save(TaskExecutionState $state) + public function save(TaskExecutionState $state): void { $group_name = $state->getGroupName(); Assert::assertSame($state, $this->getState($group_name), 'Expect to only save entities we provided'); @@ -616,7 +615,7 @@ public function save(TaskExecutionState $state) $this->saveEntity($state); } - public function assertSavedTaskTimes(string $task_name, int $count) + public function assertSavedTaskTimes(string $task_name, int $count): void { Assert::assertSame( $count, @@ -633,7 +632,7 @@ public function assertSavedTask(string $group_name): TaskExecutionState return $state; } - public function assertNothingSaved() + public function assertNothingSaved(): void { parent::assertNothingSaved(); } @@ -738,7 +737,7 @@ public function reportTimedOut( ]; } - public function assertExactCumulativeReports(array ...$expect_reports) + public function assertExactCumulativeReports(array ...$expect_reports): void { Assert::assertSame($this->reports, $expect_reports); } diff --git a/test/unit/CronTaskGroupDefinitionTest.php b/test/unit/CronTaskGroupDefinitionTest.php index 5f150cc..929eaf8 100644 --- a/test/unit/CronTaskGroupDefinitionTest.php +++ b/test/unit/CronTaskGroupDefinitionTest.php @@ -6,11 +6,13 @@ use Ingenerator\PHPUtils\DateTime\DateString; use Ingenerator\ScheduledTaskRunner\CronTaskGroupDefinition; use InvalidArgumentException; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; class CronTaskGroupDefinitionTest extends TestCase { - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(CronTaskGroupDefinition::class, $this->newSubject()); } @@ -102,10 +104,8 @@ public static function provider_scheduled_times(): array ]; } - /** - * @dataProvider provider_scheduled_times - */ - public function test_it_can_advise_next_scheduled_time_after_a_time($schedule, $time_now, $expect_next) + #[DataProvider('provider_scheduled_times')] + public function test_it_can_advise_next_scheduled_time_after_a_time($schedule, $time_now, $expect_next): void { $subject = $this->newSubject(['schedule' => $schedule]); $this->assertSame( @@ -114,12 +114,10 @@ public function test_it_can_advise_next_scheduled_time_after_a_time($schedule, $ ); } - /** - * @testWith ["junk"] - * ["every 95 years"] - * [{"hour": "95"}] - */ - public function test_it_throws_with_invalid_schedule($schedule) + #[TestWith(['junk'])] + #[TestWith(['every 95 years'])] + #[TestWith([['hour' => 95]])] + public function test_it_throws_with_invalid_schedule($schedule): void { $this->expectException(InvalidArgumentException::class); $this->newSubject(['schedule' => $schedule]); diff --git a/test/unit/PDOPausedTaskListCheckerTest.php b/test/unit/PDOPausedTaskListCheckerTest.php index 34850f4..cd5718b 100644 --- a/test/unit/PDOPausedTaskListCheckerTest.php +++ b/test/unit/PDOPausedTaskListCheckerTest.php @@ -8,6 +8,8 @@ use Ingenerator\PHPUtils\DateTime\Clock\StoppedMockClock; use Ingenerator\ScheduledTaskRunner\PausedTaskListChecker; use Ingenerator\ScheduledTaskRunner\PDOPausedTaskListChecker; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use RuntimeException; use test\mock\Ingenerator\ScheduledTaskRunner\PDO\PDOStub; @@ -23,23 +25,21 @@ class PDOPausedTaskListCheckerTest extends TestCase private PDOStub $pdo; - public function test_it_is_initialisable() + public function test_it_is_initialisable(): void { $this->assertInstanceOf(PausedTaskListChecker::class, $this->newSubject()); } - public function test_it_loads_from_database() + public function test_it_loads_from_database(): void { $this->givenDatabaseWillProvideConfig('{}', 'SELECT foo AS value FROM bar WHERE whatever'); $this->newSubject()->isPaused('anything'); $this->pdo->assertPerformedExactQueries(['SELECT foo AS value FROM bar WHERE whatever']); } - /** - * @testWith [[], "0 results"] - * [[{"foo": "ab"}, {"foo": "bc"}], "2 results"] - */ - public function test_it_throws_if_db_query_returns_zero_or_more_than_one_results($results, $expect_msg) + #[TestWith([[], '0 results'])] + #[TestWith([[['foo' => 'ab'], ['foo' => 'bc']], '2 results'])] + public function test_it_throws_if_db_query_returns_zero_or_more_than_one_results($results, $expect_msg): void { $this->pdo = PDOStub::willReturnData( [ @@ -52,7 +52,7 @@ public function test_it_throws_if_db_query_returns_zero_or_more_than_one_results $subject->isPaused('anything'); } - public function test_it_only_refreshes_from_database_at_configured_interval() + public function test_it_only_refreshes_from_database_at_configured_interval(): void { $this->givenDatabaseWillProvideConfig('{}'); $this->clock = StoppedMockClock::at('2022-06-01 00:00:00'); @@ -85,7 +85,7 @@ public function test_it_only_refreshes_from_database_at_configured_interval() ); } - public function provider_task_paused() + public static function provider_task_paused(): array { return [ [ @@ -115,13 +115,12 @@ public function provider_task_paused() ]; } - /** - * @dataProvider provider_task_paused - */ + #[DataProvider('provider_task_paused')] public function test_it_reports_task_is_paused_if_in_config_map_and_before_configured_time( string $config_json, array $expect_paused_at - ) { + ): void + { $this->givenDatabaseWillProvideConfig($config_json); $this->clock = new class() extends StoppedMockClock { @@ -130,7 +129,7 @@ public function __construct() parent::__construct(new DateTimeImmutable('2022-05-01 00:00')); } - public function tickTo(DateTimeImmutable $new_time) + public function tickTo(DateTimeImmutable $new_time): void { $this->current_microtime = (float) $new_time->format('U.u'); } From bbd4229f58d4f10dbe2815b235ee8a705ccdf538 Mon Sep 17 00:00:00 2001 From: craig410 Date: Thu, 3 Oct 2024 08:50:05 +0100 Subject: [PATCH 3/4] Exclude tests and CI config from export package --- .gitattributes | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e768e86 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,34 @@ +# Autodetect text files, normalize to LF on commit +* text=auto + +# Explicitly define files which should be LF when checked out and on commit +*.md text eol=lf +*.sh text eol=lf +*.rb text eol=lf diff=ruby +*.php text eol=lf diff=php +*.yml text eol=lf +*.sql text eol=lf +*.xml text eol=lf +*.xsd text eol=lf +*.css text eol=lf +*.js text eol=lf +*.pem text eol=lf +LICENSE text eol=lf +README text eol=lf + +# Declare files that will always have CRLF line endings on checkout (still stored LF) +*.bat text eol=crlf + +# Denote all files that are truly binary and should not be modified. +*.png binary +*.jpg binary +*.gif binary +*.ttf binary +*.swf binary +*.zip binary +*.sqlite binary + +# Ignore paths that should not be included in an archive (eg for a distribution version) +/.github export-ignore +/test export-ignore +phpunit.xml export-ignore From 2a002a17dd678432d3375c926b099c5927c4a508 Mon Sep 17 00:00:00 2001 From: craig410 Date: Thu, 3 Oct 2024 08:54:57 +0100 Subject: [PATCH 4/4] Remove --verbose option no longer supported in PHPUnit 10 --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e2261f7..314b368 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,7 @@ jobs: - name: Run unit tests run: | - vendor/bin/phpunit --testsuite=unit --verbose + vendor/bin/phpunit --testsuite=unit - name: Start MySQL server run: | @@ -66,4 +66,4 @@ jobs: - name: Run integration tests run: | - vendor/bin/phpunit --testsuite=integration --verbose + vendor/bin/phpunit --testsuite=integration