diff --git a/tests/e2e/Scopes/Scope.php b/tests/e2e/Scopes/Scope.php index bd6ef3d8aa..2a9f205272 100644 --- a/tests/e2e/Scopes/Scope.php +++ b/tests/e2e/Scopes/Scope.php @@ -2,12 +2,15 @@ namespace Tests\E2E\Scopes; +use Appwrite\Tests\Retryable; use Tests\E2E\Client; use PHPUnit\Framework\TestCase; use Utopia\Database\ID; abstract class Scope extends TestCase { + use Retryable; + /** * @var Client */ diff --git a/tests/e2e/Services/Account/AccountBase.php b/tests/e2e/Services/Account/AccountBase.php index 1b287357b8..07d7ffccf7 100644 --- a/tests/e2e/Services/Account/AccountBase.php +++ b/tests/e2e/Services/Account/AccountBase.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Account; +use Appwrite\Tests\Retry; use Tests\E2E\Client; use Utopia\Database\ID; use Utopia\Database\DateTime; @@ -519,6 +520,7 @@ trait AccountBase /** * @depends testUpdateAccountName */ + #[Retry(count: 1)] public function testUpdateAccountPassword($data): array { $email = $data['email'] ?? ''; diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 450f65f305..2678179e31 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Functions; +use Appwrite\Tests\Retry; use CURLFile; use Tests\E2E\Client; use Tests\E2E\Scopes\ProjectCustom; @@ -1209,6 +1210,7 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(204, $response['headers']['status-code']); } + #[Retry(count: 1)] public function testCreateCustomRubyExecution() { $name = 'ruby-3.1'; diff --git a/tests/e2e/Services/Webhooks/WebhooksBase.php b/tests/e2e/Services/Webhooks/WebhooksBase.php index 8ed8b2bb27..b12faf6321 100644 --- a/tests/e2e/Services/Webhooks/WebhooksBase.php +++ b/tests/e2e/Services/Webhooks/WebhooksBase.php @@ -2,6 +2,7 @@ namespace Tests\E2E\Services\Webhooks; +use Appwrite\Tests\Retry; use CURLFile; use Tests\E2E\Client; use Utopia\Database\DateTime; @@ -304,6 +305,7 @@ trait WebhooksBase /** * @depends testCreateCollection */ + #[Retry(count: 1)] public function testDeleteDocument(array $data): array { $actorsId = $data['actorsId']; diff --git a/tests/extensions/Retry.php b/tests/extensions/Retry.php new file mode 100644 index 0000000000..4a9e8d4d62 --- /dev/null +++ b/tests/extensions/Retry.php @@ -0,0 +1,16 @@ +getNumberOfRetries(); + $ex = null; + for ($i = 0; $i <= $retries; ++$i) { + try { + parent::runBare(); + return; + } catch (\Throwable | \Exception $ex) { + // Swallow the exception until we have exhausted our retries. + if ($i !== $retries) { + echo 'Flaky test failed, retrying...' . PHP_EOL; + } + } + } + if ($ex) { + throw $ex; + } + } + + /** + * @return int + * @throws \ReflectionException + */ + private function getNumberOfRetries(): int + { + $root = new \ReflectionClass($this); + $case = $this->getTestCaseRoot($root); + $name = $case->getProperty('name'); + $name->setAccessible(true); + $name = $name->getValue($this); + $method = $root->getMethod($name); + $attributes = $method->getAttributes(Retry::class); + $attribute = $attributes[0] ?? null; + $args = $attribute?->getArguments(); + $retries = $args['count'] ?? 0; + return \max(0, $retries); + } + + /** + * @param \ReflectionClass $reflection + * @return \ReflectionClass + */ + private function getTestCaseRoot(\ReflectionClass $reflection): \ReflectionClass + { + if ($reflection->getName() === TestCase::class) { + return $reflection; + } + return $this->getTestCaseRoot($reflection->getParentClass()); + } +}