diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e8b9893..ab7139e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,11 +13,11 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['7.4', '8.0', '8.1'] + php-version: ['8.1', '8.2'] composer-flags: [''] name: [''] include: - - php-version: '7.3' + - php-version: '8.1' composer-flags: '--prefer-lowest' name: '(prefer lowest dependencies)' name: Tests PHP ${{ matrix.php-version }} ${{ matrix.name }} diff --git a/bin/smoker b/bin/smoker deleted file mode 100755 index 69f80ae..0000000 --- a/bin/smoker +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env php -add(new SmokerCommand()) - ->getApplication() - ->setDefaultCommand('smoke', true) - ->run() -; diff --git a/composer.json b/composer.json index 15e13c3..479d492 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,7 @@ } ], "bin": [ - "bin/asynit", - "bin/smoker" + "bin/asynit" ], "autoload": { "psr-4": { @@ -27,24 +26,31 @@ "test": "bin/asynit tests/" }, "require": { - "php": "^7.3 || ^8.0", - "amphp/http-client": "^3.0.14", - "amphp/sync": "^1.0", - "bovigo/assert": "^6.2", - "doctrine/annotations": "^1.2", - "doctrine/collections": "^1.3", - "guzzlehttp/psr7": "^1.4", - "php-http/client-common": "^2.0", - "php-http/message": "^1.3", + "php": "^8.1", + "amphp/amp": "^3.0", + "amphp/sync": "^2.0", + "bovigo/assert": "^7.0", "symfony/console": "^4.4 || ^5.0 || ^6.0", - "symfony/dom-crawler": "^4.4 || ^5.0 || ^6.0", - "symfony/finder": "^4.4 || ^5.0 || ^6.0", - "symfony/yaml": "^4.4 || ^5.0 || ^6.0" + "symfony/finder": "^4.4 || ^5.0 || ^6.0" + }, + "suggest": { + "amphp/http-client": "To use the web test case trait", + "nyholm/psr7": "To use the web test case trait" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.10" + "amphp/http-client": "v5.0.0-beta.11", + "friendsofphp/php-cs-fixer": "^v2.19.3", + "nyholm/psr7": "^1.8.0", + "php-http/client-common": "^2.4", + "php-http/message": "^1.3", + "phpstan/phpstan": "^1.10" + }, + "conflict": { + "amphp/http-client": "= 5.0" }, "prefer-stable": true, + "minimum-stability": "dev", "config": { "sort-packages": true } diff --git a/example/HttpClientOverrideTest.php b/example/HttpClientOverrideTest.php index c7321c1..fb73e49 100644 --- a/example/HttpClientOverrideTest.php +++ b/example/HttpClientOverrideTest.php @@ -2,7 +2,7 @@ class HttpClientOverrideTest extends \Asynit\TestCase { - public function setUp(\Http\Client\HttpAsyncClient $asyncClient): \Http\Client\HttpAsyncClient + public function setUp(Http\Client\HttpAsyncClient $asyncClient): Http\Client\HttpAsyncClient { $uri = (new \Http\Message\UriFactory\GuzzleUriFactory())->createUri('http://httpbin.org'); diff --git a/src/Annotation/Depend.php b/src/Annotation/Depend.php deleted file mode 100644 index 44429d1..0000000 --- a/src/Annotation/Depend.php +++ /dev/null @@ -1,27 +0,0 @@ -dependency = $dependency['value']; - } - - /** - * @return mixed - */ - public function getDependency() - { - return $this->dependency; - } -} diff --git a/src/Annotation/DisplayName.php b/src/Annotation/DisplayName.php deleted file mode 100644 index c989411..0000000 --- a/src/Annotation/DisplayName.php +++ /dev/null @@ -1,28 +0,0 @@ -name = $name; - } - - public function getName(): string - { - return $this->name; - } -} diff --git a/src/Assert/AssertCaseTrait.php b/src/Assert/AssertCaseTrait.php index 0f187ec..4fc53cf 100644 --- a/src/Assert/AssertCaseTrait.php +++ b/src/Assert/AssertCaseTrait.php @@ -4,6 +4,8 @@ namespace Asynit\Assert; +use Asynit\Attribute\OnCreate; +use Asynit\Test; use function bovigo\assert\counting; use function bovigo\assert\exporter; use function bovigo\assert\predicate\contains; @@ -47,7 +49,13 @@ trait AssertCaseTrait { - private $test; + protected Test $test; + + #[OnCreate] + public function setUpTest(Test $test): void + { + $this->test = $test; + } /** * Asserts that an array has a specified key. diff --git a/src/Assert/Assertion.php b/src/Assert/Assertion.php index ecf4433..bb83fe4 100644 --- a/src/Assert/Assertion.php +++ b/src/Assert/Assertion.php @@ -12,22 +12,11 @@ class Assertion extends BaseAssertion { - /** - * value to do the assertion on. - * - * @var mixed - */ - private $value; + private mixed $value; - /** - * @var \SebastianBergmann\Exporter\Exporter - */ - private $exporter; + private Exporter $exporter; - /** - * @var Test - */ - private $test; + private Test $test; /** * constructor. @@ -43,9 +32,6 @@ public function __construct($value, Exporter $exporter, Test $test) $this->test = $test; } - /** @var Test */ - public static $currentTest; - public function evaluate(Predicate $predicate, string $description = null): bool { try { diff --git a/src/Attribute/Depend.php b/src/Attribute/Depend.php new file mode 100644 index 0000000..b49d614 --- /dev/null +++ b/src/Attribute/Depend.php @@ -0,0 +1,11 @@ +getOption('order')))->buildOutput(\count($testMethods)); - // Build services for parsing and running tests - $builder = new TestPoolBuilder(new AnnotationReader()); - $runner = new PoolRunner(new GuzzleMessageFactory(), new TestWorkflow($chainOutput), $input->getOption('concurrency')); + $builder = new TestPoolBuilder(); + $runner = new PoolRunner(new TestWorkflow($chainOutput), $input->getOption('concurrency')); // Build a list of tests from the directory $pool = $builder->build($testMethods); - - Loop::run(function () use ($runner, $pool) { - $runner->loop($pool); - }); + $runner->loop($pool); // Return the number of failed tests return $countOutput->getFailed(); diff --git a/src/Command/SmokerCommand.php b/src/Command/SmokerCommand.php deleted file mode 100644 index efb03c6..0000000 --- a/src/Command/SmokerCommand.php +++ /dev/null @@ -1,61 +0,0 @@ -setName('smoke') - ->addArgument('file', InputArgument::REQUIRED, 'Configuration file for smoker') - ->addOption('host', null, InputOption::VALUE_OPTIONAL, 'Base host to use', null) - ->addOption('allow-self-signed-certificate', null, InputOption::VALUE_NONE, 'Allow self signed ssl certificate') - ->addOption('concurrency', null, InputOption::VALUE_OPTIONAL, 'Max number of parallels requests', 10) - ; - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - // Build the client - $parser = new SmokeParser(); - $testMethods = $parser->parse($input->getArgument('file'), $input->getOption('host')); - - list($chainOutput, $countOutput) = (new OutputFactory())->buildOutput(\count($testMethods)); - - $builder = new TestPoolBuilder(new AnnotationReader()); - $runner = new PoolRunner(new GuzzleMessageFactory(), new TestWorkflow($chainOutput), $input->getOption('concurrency'), $input->getOption('allow-self-signed-certificate')); - - $pool = $builder->build($testMethods); - - // Run the list of tests - Loop::run(function () use ($runner, $pool) { - $runner->loop($pool); - }); - - // Return the number of failed tests - return $countOutput->getFailed(); - } -} diff --git a/src/HttpClient/AmpPsrHttpClient.php b/src/HttpClient/AmpPsrHttpClient.php new file mode 100644 index 0000000..6c9cd3a --- /dev/null +++ b/src/HttpClient/AmpPsrHttpClient.php @@ -0,0 +1,48 @@ +getUri(), + $request->getMethod(), + $request->getBody()->getContents(), + ); + + foreach ($request->getHeaders() as $name => $values) { + $ampRequest->addHeader($name, $values); + } + + $ampResponse = $this->client->request($ampRequest); + + $response = $this->responseFactory->createResponse( + $ampResponse->getStatus(), + ); + + foreach ($ampResponse->getHeaders() as $name => $values) { + $response = $response->withHeader($name, $values); + } + + $body = $this->streamFactory->createStream($ampResponse->getBody()->buffer()); + + return $response->withBody($body); + } +} diff --git a/src/HttpClient/ArtaxAsyncAdapter.php b/src/HttpClient/ArtaxAsyncAdapter.php deleted file mode 100644 index 0253f74..0000000 --- a/src/HttpClient/ArtaxAsyncAdapter.php +++ /dev/null @@ -1,62 +0,0 @@ -client = $client; - $this->responseFactory = $responseFactory; - } - - /** - * {@inheritdoc} - */ - public function sendAsyncRequest(RequestInterface $request) - { - return new PromiseAdapter(\Amp\call(function () use ($request) { - $cancellationTokenSource = new CancellationTokenSource(); - - $req = new \Amp\Artax\Request($request->getUri(), $request->getMethod()); - $req = $req->withProtocolVersions([$request->getProtocolVersion()]); - $req = $req->withHeaders($request->getHeaders()); - $req = $req->withBody((string) $request->getBody()); - - try { - /** @var Artax\Response $response */ - $response = yield $this->client->request($req, [ - Artax\Client::OP_MAX_REDIRECTS => 0, - ], $cancellationTokenSource->getToken()); - } catch (Artax\HttpException $e) { - throw new RequestException($e->getMessage(), $request, $e); - } - - return $this->responseFactory->createResponse( - $response->getStatus(), - $response->getReason(), - $response->getHeaders(), - yield $response->getBody(), - $response->getProtocolVersion() - ); - })); - } -} diff --git a/src/HttpClient/HttpClientWebCaseTrait.php b/src/HttpClient/HttpClientWebCaseTrait.php new file mode 100644 index 0000000..9d6ea0e --- /dev/null +++ b/src/HttpClient/HttpClientWebCaseTrait.php @@ -0,0 +1,111 @@ +withoutPeerVerification(); + } + + $connectContext = new ConnectContext(''); + $connectContext = $connectContext->withTlsContext($tlsContext); + + $builder = new HttpClientBuilder(); + $builder = $builder->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(null, $connectContext))); + $client = $builder->build(); + $factory = new Psr17Factory(); + + return new AmpPsrHttpClient($client, $factory, $factory); + } + + #[OnCreate] + final public function setUpHttpClient(): void + { + $this->httpClient = $this->createHttpClient($this->allowSelfSignedCertificate); + $this->httpFactory = new Psr17Factory(); + } + + /** + * Allow to test a rejection or a resolution of an async call. + */ + final protected function sendRequest(RequestInterface $request): ResponseInterface + { + return $this->httpClient->sendRequest($request); + } + + final protected function get(string $uri, array $headers = [], $body = null, ?string $version = null): ResponseInterface + { + return $this->sendRequest($this->createRequest('GET', $uri, $headers, $body, $version)); + } + + final protected function post(string $uri, array $headers = [], $body = null, ?string $version = null): ResponseInterface + { + return $this->sendRequest($this->createRequest('POST', $uri, $headers, $body, $version)); + } + + final protected function patch(string $uri, array $headers = [], $body = null, ?string $version = null): ResponseInterface + { + return $this->sendRequest($this->createRequest('PATCH', $uri, $headers, $body, $version)); + } + + final protected function put(string $uri, array $headers = [], $body = null, ?string $version = null): ResponseInterface + { + return $this->sendRequest($this->createRequest('PUT', $uri, $headers, $body, $version)); + } + + final protected function delete(string $uri, array $headers = [], $body = null, ?string $version = null): ResponseInterface + { + return $this->sendRequest($this->createRequest('DELETE', $uri, $headers, $body, $version)); + } + + final protected function options(string $uri, array $headers = [], $body = null, ?string $version = null): ResponseInterface + { + return $this->sendRequest($this->createRequest('OPTIONS', $uri, $headers, $body, $version)); + } + + private function createRequest(string $method, string $uri, array $headers = [], $body = null, ?string $version = null): RequestInterface + { + $request = $this->httpFactory->createRequest($method, $uri); + + foreach ($headers as $name => $value) { + $request = $request->withHeader($name, $value); + } + + if (null !== $body) { + $body = $this->httpFactory->createStream($body); + + $request = $request->withBody($body); + } + + if (null !== $version) { + $request = $request->withProtocolVersion($version); + } + + return $request; + } +} diff --git a/src/HttpClient/PromiseAdapter.php b/src/HttpClient/PromiseAdapter.php deleted file mode 100644 index 91bcbf9..0000000 --- a/src/HttpClient/PromiseAdapter.php +++ /dev/null @@ -1,133 +0,0 @@ -promise = $promise; - $this->promise->onResolve(function ($error, $result) { - if (null !== $error) { - if (!$error instanceof Exception) { - $error = new Exception\TransferException($error->getMessage(), $error->getCode(), $error); - } - - $this->reject($error); - } else { - $this->resolve($result); - } - }); - } - - public function then(callable $onFulfilled = null, callable $onRejected = null) - { - $deferred = new \Amp\Deferred(); - $newPromise = new self($deferred->promise()); - - $onFulfilled = $onFulfilled ?? function (ResponseInterface $response) { - return $response; - }; - - $onRejected = $onRejected ?? function (Exception $exception) { - throw $exception; - }; - - $this->onFulfilled = function (ResponseInterface $response) use ($onFulfilled, $deferred) { - try { - $deferred->resolve($onFulfilled($response)); - } catch (Exception $exception) { - $deferred->fail($exception); - } catch (\Throwable $error) { - $deferred->fail(new Exception\TransferException($error->getMessage(), $error->getCode(), $error)); - } - }; - $this->onRejected = function (Exception $exception) use ($onRejected, $deferred) { - try { - $deferred->resolve($onRejected($exception)); - } catch (Exception $exception) { - $deferred->fail($exception); - } catch (\Throwable $error) { - $deferred->fail(new Exception\TransferException($error->getMessage(), $error->getCode(), $error)); - } - }; - - if (HttpPromise::FULFILLED === $this->state) { - $this->resolve($this->response); - } - - if (HttpPromise::REJECTED === $this->state) { - $this->reject($this->exception); - } - - return $newPromise; - } - - private function resolve(ResponseInterface $response) - { - $this->state = HttpPromise::FULFILLED; - $this->response = $response; - $onFulfilled = $this->onFulfilled; - - if (null !== $onFulfilled) { - $onFulfilled($response); - } - } - - private function reject(Exception $exception) - { - $this->state = HttpPromise::REJECTED; - $this->exception = $exception; - $onRejected = $this->onRejected; - - if (null !== $onRejected) { - $onRejected($exception); - } - } - - public function getState() - { - return $this->state; - } - - public function wait($unwrap = true) - { - AmpPromise\wait($this->promise); - - if ($unwrap) { - if (HttpPromise::REJECTED === $this->getState()) { - throw $this->exception; - } - - return $this->response; - } - } - - public function onResolve(callable $onResolved) - { - $this->promise->onResolve($onResolved); - } -} diff --git a/src/Output/OutputOrder.php b/src/Output/OutputOrder.php index c63a43c..55317d3 100644 --- a/src/Output/OutputOrder.php +++ b/src/Output/OutputOrder.php @@ -1,65 +1,65 @@ -tests[] = $test; - } - - public function outputSuccess(Test $test, $debugOutput) - { - $this->tests[] = $test; - } - - public function outputSkipped(Test $test, $debugOutput) - { - } - - public function __destruct() - { - fwrite(STDOUT, "\nTest orders:\n\n"); - - $orders = []; - - foreach ($this->tests as $index => $test) { - $depends = $this->createDepends($test, $orders); - $orders[$test->getDisplayName()] = $index; - $dependsStr = ""; - - if (\count($depends) > 0) { - $dependsStr = " depends on " . join(", ", array_map(function ($i) { - return "#" . $i; - }, array_unique($depends))); - } - - fwrite(STDOUT, " - #".$index . ' ' . $test->getDisplayName() . $dependsStr . "\n"); - } - } - - public function createDepends(Test $test, array $orders = []): array - { - $depends = []; - - foreach ($test->getParents() as $parentTest) { - $depends = array_merge($depends, $this->createDepends($parentTest, $orders)); - } - - if (\array_key_exists($test->getDisplayName(), $orders)) { - $depends[] = $orders[$test->getDisplayName()]; - } - - return $depends; - } -} +tests[] = $test; + } + + public function outputSuccess(Test $test, $debugOutput) + { + $this->tests[] = $test; + } + + public function outputSkipped(Test $test, $debugOutput) + { + } + + public function __destruct() + { + fwrite(STDOUT, "\nTest orders:\n\n"); + + $orders = []; + + foreach ($this->tests as $index => $test) { + $depends = $this->createDepends($test, $orders); + $orders[$test->getDisplayName()] = $index; + $dependsStr = ''; + + if (\count($depends) > 0) { + $dependsStr = ' depends on '.join(', ', array_map(function ($i) { + return '#'.$i; + }, array_unique($depends))); + } + + fwrite(STDOUT, ' - #'.$index.' '.$test->getDisplayName().$dependsStr."\n"); + } + } + + public function createDepends(Test $test, array $orders = []): array + { + $depends = []; + + foreach ($test->getParents() as $parentTest) { + $depends = array_merge($depends, $this->createDepends($parentTest, $orders)); + } + + if (\array_key_exists($test->getDisplayName(), $orders)) { + $depends[] = $orders[$test->getDisplayName()]; + } + + return $depends; + } +} diff --git a/src/Parser/SmokeParser.php b/src/Parser/SmokeParser.php deleted file mode 100644 index f8a4c28..0000000 --- a/src/Parser/SmokeParser.php +++ /dev/null @@ -1,30 +0,0 @@ - $configuration) { - $url = $host . $url; - $test = new SmokeTest(new \ReflectionMethod(SmokerTestCase::class, 'smokeTest'), $url); - $argument = [$url, $configuration, $test]; - $test->addArgument($argument, $test); - - $methods[$url] = $test; - } - - return $methods; - } -} diff --git a/src/Parser/TestPoolBuilder.php b/src/Parser/TestPoolBuilder.php index 0adf504..b6b2a35 100644 --- a/src/Parser/TestPoolBuilder.php +++ b/src/Parser/TestPoolBuilder.php @@ -2,24 +2,16 @@ namespace Asynit\Parser; -use Asynit\Annotation\Depend; -use Asynit\Annotation\DisplayName; +use Asynit\Attribute\Depend; +use Asynit\Attribute\DisplayName; use Asynit\Pool; use Asynit\Test; -use Doctrine\Common\Annotations\AnnotationReader; /** * Build test. */ -class TestPoolBuilder +final class TestPoolBuilder { - private $reader; - - public function __construct(AnnotationReader $reader) - { - $this->reader = $reader; - } - /** * Build the initial test pool. * @@ -43,44 +35,45 @@ public function build(array $tests): Pool private function processTestAnnotations(\ArrayObject $tests, Test $test) { - $annotations = $this->reader->getMethodAnnotations($test->getMethod()); - - foreach ($annotations as $annotation) { - if ($annotation instanceof Depend) { - $dependency = $annotation->getDependency(); - - if ($tests->offsetExists($dependency)) { - $dependentTest = $tests->offsetGet($dependency); - $dependentTest->addChildren($test); - $test->addParent($dependentTest); - continue; - } - - if (false === strpos($dependency, '::')) { - $class = $test->getMethod()->getDeclaringClass()->getName(); - $method = $dependency; - } else { - [$class, $method] = explode('::', $dependency, 2); - } - - if (!method_exists($class, $method)) { - throw new \RuntimeException(sprintf('Failed to build test pool "%s" dependency is not resolvable for "%s::%s".', $dependency, $test->getMethod()->getDeclaringClass()->getName(), $test->getMethod()->getName())); - } + $testMethod = $test->getMethod(); + $attributes = $testMethod->getAttributes(Depend::class); - $dependentTest = new Test(new \ReflectionMethod($class, $method), null, false); - if ($tests->offsetExists($dependentTest->getIdentifier())) { - $dependentTest = $tests->offsetGet($dependentTest->getIdentifier()); - } else { - $tests[$dependentTest->getIdentifier()] = $dependentTest; - } + foreach ($attributes as $attribute) { + $dependency = $attribute->newInstance()->dependency; + if ($tests->offsetExists($dependency)) { + $dependentTest = $tests->offsetGet($dependency); $dependentTest->addChildren($test); $test->addParent($dependentTest); + continue; } - if ($annotation instanceof DisplayName) { - $test->setDisplayName($annotation->getName()); + if (false === strpos($dependency, '::')) { + $class = $test->getMethod()->getDeclaringClass()->getName(); + $method = $dependency; + } else { + [$class, $method] = explode('::', $dependency, 2); } + + if (!method_exists($class, $method)) { + throw new \RuntimeException(sprintf('Failed to build test pool "%s" dependency is not resolvable for "%s::%s".', $dependency, $test->getMethod()->getDeclaringClass()->getName(), $test->getMethod()->getName())); + } + + $dependentTest = new Test(new \ReflectionMethod($class, $method), null, false); + if ($tests->offsetExists($dependentTest->getIdentifier())) { + $dependentTest = $tests->offsetGet($dependentTest->getIdentifier()); + } else { + $tests[$dependentTest->getIdentifier()] = $dependentTest; + } + + $dependentTest->addChildren($test); + $test->addParent($dependentTest); + } + + $displayName = $testMethod->getAttributes(DisplayName::class); + + if (\count($displayName) > 0) { + $test->setDisplayName($displayName[0]->newInstance()->name); } } } diff --git a/src/Parser/TestsFinder.php b/src/Parser/TestsFinder.php index 4ebab5b..d84c59e 100644 --- a/src/Parser/TestsFinder.php +++ b/src/Parser/TestsFinder.php @@ -2,8 +2,9 @@ namespace Asynit\Parser; +use Asynit\Attribute\Test as TestAnnotation; +use Asynit\Attribute\TestCase; use Asynit\Test; -use Asynit\TestCase; use Symfony\Component\Finder\Finder; class TestsFinder @@ -40,17 +41,26 @@ private function doFindTests($files): array $newClasses = array_diff(get_declared_classes(), $existingClasses); foreach ($newClasses as $class) { - if (!is_subclass_of($class, TestCase::class)) { + $reflectionClass = new \ReflectionClass($class); + $testCases = $reflectionClass->getAttributes(TestCase::class); + + if (0 === count($testCases)) { continue; } - foreach (get_class_methods($class) as $method) { - if (!preg_match('/^test(.+)$/', $method)) { - continue; + foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) { + $tests = $reflectionMethod->getAttributes(TestAnnotation::class); + $test = null; + + if (count($tests) > 0) { + $test = new Test($reflectionMethod); + } elseif (preg_match('/^test(.+)$/', $reflectionMethod->getName())) { + $test = new Test($reflectionMethod); } - $test = new Test(new \ReflectionMethod($class, $method)); - $methods[$test->getIdentifier()] = $test; + if (null !== $test) { + $methods[$test->getIdentifier()] = $test; + } } } } diff --git a/src/Pool.php b/src/Pool.php index d1ebfc8..61c396a 100644 --- a/src/Pool.php +++ b/src/Pool.php @@ -2,19 +2,17 @@ namespace Asynit; -use Doctrine\Common\Collections\ArrayCollection; - /** * Pool containing test, running tests and running http calls. */ class Pool { - /** @var Test[]|ArrayCollection */ + /** @var Test[] */ private $tests; public function __construct() { - $this->tests = new ArrayCollection(); + $this->tests = []; } /** @@ -22,18 +20,16 @@ public function __construct() */ public function addTest(Test $test) { - $this->tests->add($test); - - if ($test instanceof PoolAwareInterface) { - $test->setPool($this); - } + $this->tests[] = $test; } public function isEmpty(): bool { - return 0 === $this->tests->filter(function (Test $test) { + $notCompletedTests = array_filter($this->tests, function (Test $test) { return !$test->isCompleted(); - })->count(); + }); + + return 0 === count($notCompletedTests); } public function getTests() diff --git a/src/PoolAwareInterface.php b/src/PoolAwareInterface.php deleted file mode 100644 index bb2c93d..0000000 --- a/src/PoolAwareInterface.php +++ /dev/null @@ -1,8 +0,0 @@ -requestFactory = $requestFactory; - $this->workflow = $workflow; $this->semaphore = new LocalSemaphore($concurrency); - $this->allowSelfSignedCertificate = $allowSelfSignedCertificate; } public function loop(Pool $pool) { - return \Amp\call(function () use ($pool) { - ob_start(); - $promises = []; - - while (!$pool->isEmpty()) { - $test = $pool->getTestToRun(); + ob_start(); + /** @var Future[] $futures */ + $futures = []; - if (null === $test) { - yield \Amp\Promise\first($promises); + while (!$pool->isEmpty()) { + $test = $pool->getTestToRun(); - continue; - } + if (null === $test) { + Future\awaitAny($futures); - $promises[$test->getIdentifier()] = $this->run($test); - $promises[$test->getIdentifier()]->onResolve(function () use (&$promises, $test) { - unset($promises[$test->getIdentifier()]); - }); + continue; } - yield $promises; + $this->workflow->markTestAsRunning($test); - Loop::stop(); - ob_end_flush(); - }); + $futures[$test->getIdentifier()] = async(function () use ($test, &$futures) { + $lock = $this->semaphore->acquire(); + $this->run($test); + $lock->release(); + + unset($futures[$test->getIdentifier()]); + }); + } + ob_end_flush(); } - protected function run(Test $test): Promise + protected function run(Test $test) { - return \Amp\call(function () use ($test) { + try { + $testCase = $this->getTestCase($test); + + $method = $test->getMethod()->getName(); + $args = $test->getArguments(); + + set_error_handler(__CLASS__.'::handleInternalError'); + try { - $this->workflow->markTestAsRunning($test); + $result = $testCase->$method(...$args); + } finally { + restore_error_handler(); + } - $testCase = $this->buildTestCase($test); + foreach ($test->getChildren() as $childTest) { + $childTest->addArgument($result, $test); + } - yield $testCase->initialize($this->allowSelfSignedCertificate); + $this->workflow->markTestAsSuccess($test); + } catch (\Throwable $error) { + $this->workflow->markTestAsFailed($test, $error); + } + } - $result = yield \Amp\call(function () use ($testCase, $test) { - $method = $test->getMethod()->getName(); - $args = $test->getArguments(); + private function getTestCase(Test $test) + { + $reflectionClass = $test->getMethod()->getDeclaringClass(); - set_error_handler(__CLASS__.'::handleInternalError'); + if (!isset($this->testCases[$reflectionClass->getName()])) { + $testCase = $reflectionClass->newInstance(); - try { - return $testCase->$method(...$args); - } finally { - restore_error_handler(); - } - }); + // Find all methods with attribute OnCreate + foreach ($reflectionClass->getMethods() as $reflectionMethod) { + $onCreate = $reflectionMethod->getAttributes(OnCreate::class); - foreach ($test->getChildren() as $childTest) { - $childTest->addArgument($result, $test); + if (0 === count($onCreate)) { + continue; } - $this->workflow->markTestAsSuccess($test); - } catch (\Throwable $error) { - $this->workflow->markTestAsFailed($test, $error); + $testCase->{$reflectionMethod->getName()}($test); } - }); - } - private function buildTestCase(Test $test): TestCase - { - return $test->getMethod()->getDeclaringClass()->newInstance($this->requestFactory, $this->semaphore, $test); + $this->testCases[$reflectionClass->getName()] = $testCase; + } + + return $this->testCases[$reflectionClass->getName()]; } public static function handleInternalError($type, $message, $file, $line) diff --git a/src/SmokeTest.php b/src/SmokeTest.php deleted file mode 100644 index 52f88ad..0000000 --- a/src/SmokeTest.php +++ /dev/null @@ -1,18 +0,0 @@ -pool = $pool; - } - - public function getPool(): ?Pool - { - return $this->pool; - } -} diff --git a/src/SmokerTestCase.php b/src/SmokerTestCase.php deleted file mode 100644 index 3592a33..0000000 --- a/src/SmokerTestCase.php +++ /dev/null @@ -1,85 +0,0 @@ -get($uri); - - if (is_array($configuration['status'])) { - static::assertContains($response->getStatusCode(), $configuration['status']); - } else { - static::assertStatusCode($configuration['status'], $response); - } - - if (!isset($configuration['discovery'])) { - return; - } - - $discovery = $configuration['discovery']; - - self::$uris[$uri] = true; - - if ( - (isset($discovery['enabled']) && !$discovery['enabled']) // Enabled by default - || (isset($discovery['depth']) && 0 === $discovery['depth']) - || $this->hasReachedDiscoveryLimit($discovery) - ) { - return; - } - - $recursiveConfiguration = $configuration; - $recursiveConfiguration['discovery']['limit'] = $discovery['limit'] ?? self::DISCOVERY_DEFAULT_LIMIT; - - if (isset($discovery['depth']) && $discovery['depth'] >= 1) { - $recursiveConfiguration['discovery']['depth'] = ((int) $discovery['depth']) - 1; - } else { - $recursiveConfiguration['discovery']['depth'] = -1; - } - - $crawler = new Crawler((string) $response->getBody(), $uri); - $links = $crawler->filterXPath('//a')->links(); - - foreach ($links as $link) { - $uri = $link->getUri(); - $fragment = parse_url($uri, PHP_URL_FRAGMENT); - $uri = str_replace('#'.$fragment, '', $uri); - - if ($this->hasReachedDiscoveryLimit($discovery)) { - return; - } - - if ( - isset(self::$uris[$uri]) - || (isset($discovery['match']) && !preg_match(sprintf('~%s~i', $discovery['match']), $uri)) - ) { - continue; - } - - $childTest = new SmokeTest(new \ReflectionMethod(self::class, 'smokeTest'), $uri); - $argument = [$uri, $recursiveConfiguration, $childTest]; - $childTest->addArgument($argument, $childTest); - - $test->getPool()->addTest($childTest); - self::$uris[$uri] = true; - } - } - - private function hasReachedDiscoveryLimit(array $discovery) - { - return count(self::$uris) >= ($discovery['limit'] ?? self::DISCOVERY_DEFAULT_LIMIT); - } -} diff --git a/src/TestCase.php b/src/TestCase.php deleted file mode 100644 index ba04a11..0000000 --- a/src/TestCase.php +++ /dev/null @@ -1,146 +0,0 @@ -messageFactory = $messageFactory; - $this->semaphore = $semaphore; - $this->test = $test; - } - - /** - * Run before each test. - * - * Allow to set default services and context, and also decorate the http async client. - * - * @return \Generator|Promise|HttpAsyncClient - */ - public function setUp(HttpAsyncClient $asyncClient) - { - return $asyncClient; - } - - final public function initialize(bool $allowSelfSignedCertificate = false) - { - return call(function () use ($allowSelfSignedCertificate) { - $this->client = yield call(function () use ($allowSelfSignedCertificate) { - if ($allowSelfSignedCertificate) { - $tlsContext = new ClientTlsContext(); - $tlsContext = $tlsContext->withoutPeerVerification(); - } - - return $this->setUp(new ArtaxAsyncAdapter($this->messageFactory, new DefaultClient(null, null, $tlsContext ?? null))); - }); - }); - } - - /** - * Allow to test a rejection or a resolution of an async call. - */ - final protected function sendRequest(RequestInterface $request): Promise - { - return \Amp\call(function () use ($request) { - /** @var Lock $lock */ - $lock = yield $this->semaphore->acquire(); - $response = yield $this->client->sendAsyncRequest($request); - - $lock->release(); - - return $response; - }); - } - - /** - * @param $uri - * @param array $headers - * @param null $body - * @param string $version - */ - final protected function get($uri, $headers = [], $body = null, $version = '1.1'): Promise - { - return $this->sendRequest($this->messageFactory->createRequest('GET', $uri, $headers, $body, $version)); - } - - /** - * @param $uri - * @param array $headers - * @param null $body - * @param string $version - */ - final protected function post($uri, $headers = [], $body = null, $version = '1.1'): Promise - { - return $this->sendRequest($this->messageFactory->createRequest('POST', $uri, $headers, $body, $version)); - } - - /** - * @param $uri - * @param array $headers - * @param null $body - * @param string $version - */ - final protected function patch($uri, $headers = [], $body = null, $version = '1.1'): Promise - { - return $this->sendRequest($this->messageFactory->createRequest('PATCH', $uri, $headers, $body, $version)); - } - - /** - * @param $uri - * @param array $headers - * @param null $body - * @param string $version - * - * @return Promise - */ - final protected function put($uri, $headers = [], $body = null, $version = '1.1') - { - return $this->sendRequest($this->messageFactory->createRequest('PUT', $uri, $headers, $body, $version)); - } - - /** - * @param $uri - * @param array $headers - * @param null $body - * @param string $version - */ - final protected function delete($uri, $headers = [], $body = null, $version = '1.1'): Promise - { - return $this->sendRequest($this->messageFactory->createRequest('DELETE', $uri, $headers, $body, $version)); - } - - /** - * @param $uri - * @param array $headers - * @param null $body - * @param string $version - */ - final protected function options($uri, $headers = [], $body = null, $version = '1.1'): Promise - { - return $this->sendRequest($this->messageFactory->createRequest('OPTIONS', $uri, $headers, $body, $version)); - } -} diff --git a/tests/AnotherTest.php b/tests/AnotherTest.php index a0b71b8..98f3924 100644 --- a/tests/AnotherTest.php +++ b/tests/AnotherTest.php @@ -2,9 +2,7 @@ namespace Asynit\Tests; -use Asynit\TestCase; - -class AnotherTest extends TestCase +class AnotherTest { public function test_from_another_file() { diff --git a/tests/FunctionalHttpTests.php b/tests/FunctionalHttpTests.php new file mode 100644 index 0000000..17e24a8 --- /dev/null +++ b/tests/FunctionalHttpTests.php @@ -0,0 +1,127 @@ +get($this->createUri('/')); + + $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertStatusCode(200, $response); + + return 'foo'; + } + + public function testException() + { + $exception = null; + + try { + $response = $this->get('http://something-is-not-reachable'); + } catch (\Exception $e) { + $exception = $e; + } + + $this->assertNotNull($exception, 'Not null exception'); + } + + #[Depend('testGet')] + public function testDepend($value) + { + $this->assertSame('foo', $value); + } + + #[Depend("Asynit\Tests\AnotherTest::test_from_another_file")] + public function testDependFromAnotherFile($value) + { + $this->assertSame('Asynit\Tests\AnotherTest::test_from_another_file', $value); + } + + public function testStartParallel() + { + return time(); + } + + public function testParallel1() + { + $this->get($this->createUri('/delay/1')); + } + + public function testParallel2() + { + $this->get($this->createUri('/delay/3')); + } + + public function testParallel3() + { + $this->get($this->createUri('/delay/5')); + } + + public function testParallel4() + { + $this->get($this->createUri('/delay/7')); + } + + #[Depend('testStartParallel')] + #[Depend('testParallel1')] + #[Depend('testParallel2')] + #[Depend('testParallel3')] + #[Depend('testParallel4')] + public function testEndParallel($start) + { + $end = time(); + + $this->assertLessThan(10, $end - $start); + } + + public function get_a() + { + return 'a'; + } + + #[Depend('get_a')] + public function get_b($a) + { + $this->assertSame('a', $a); + + return 'b'; + } + + #[Depend('get_a')] + #[Depend('get_b')] + public function test_c($a, $b) + { + $this->assertSame('a', $a); + $this->assertSame('b', $b); + } + + #[Depend('get_a')] + #[Depend('get_b')] + #[Depend("Asynit\Tests\AnotherTest::get_d")] + public function test_c_with_d($a, $b, $d) + { + $this->assertSame('a', $a); + $this->assertSame('b', $b); + $this->assertSame('d', $d); + } + + protected function createUri(string $uri): string + { + return 'http://127.0.0.1:8081'.$uri; + } +} diff --git a/tests/FunctionalTests.php b/tests/FunctionalTests.php index 63d82d3..be76555 100644 --- a/tests/FunctionalTests.php +++ b/tests/FunctionalTests.php @@ -2,51 +2,22 @@ namespace Asynit\Tests; -use Asynit\Annotation\Depend; -use Asynit\TestCase; -use Http\Client\Exception; -use Psr\Http\Message\ResponseInterface; +use function Amp\delay; +use Asynit\Attribute\Depend; +use Asynit\Attribute\TestCase; +use Asynit\Assert\AssertCaseTrait; -class FunctionalTests extends TestCase +#[TestCase] +class FunctionalTests { + use AssertCaseTrait; + public function testReturn() { return 'tata'; } - public function testGet() - { - $response = yield $this->get('http://127.0.0.1:8081'); - - $this->assertInstanceOf(ResponseInterface::class, $response); - $this->assertStatusCode(200, $response); - - return 'foo'; - } - - public function testException() - { - $exception = null; - - try { - $response = yield $this->get('http://something-is-not-reachable'); - } catch (\Exception $e) { - $exception = $e; - } - - $this->assertNotNull($exception, 'Not null exception'); - $this->assertInstanceOf(Exception::class, $exception); - } - - /** - * @Depend("testGet") - */ - public function testDepend($value) - { - $this->assertSame('foo', $value); - } - - /** @Depend("Asynit\Tests\AnotherTest::test_from_another_file") */ + #[Depend("Asynit\Tests\AnotherTest::test_from_another_file")] public function testDependFromAnotherFile($value) { $this->assertSame('Asynit\Tests\AnotherTest::test_from_another_file', $value); @@ -59,31 +30,29 @@ public function testStartParallel() public function testParallel1() { - yield $this->get('http://127.0.0.1:8081/delay/7'); + delay(4); } public function testParallel2() { - yield $this->get('http://127.0.0.1:8081/delay/7'); + delay(5); } public function testParallel3() { - yield $this->get('http://127.0.0.1:8081/delay/7'); + delay(6); } public function testParallel4() { - yield $this->get('http://127.0.0.1:8081/delay/7'); + delay(7); } - /** - * @Depend("testStartParallel") - * @Depend("testParallel1") - * @Depend("testParallel2") - * @Depend("testParallel3") - * @Depend("testParallel4") - */ + #[Depend('testStartParallel')] + #[Depend('testParallel1')] + #[Depend('testParallel2')] + #[Depend('testParallel3')] + #[Depend('testParallel4')] public function testEndParallel($start) { $end = time(); @@ -96,7 +65,7 @@ public function get_a() return 'a'; } - /** @Depend("get_a") */ + #[Depend('get_a')] public function get_b($a) { $this->assertSame('a', $a); @@ -104,21 +73,17 @@ public function get_b($a) return 'b'; } - /** - * @Depend("get_a") - * @Depend("get_b") - */ + #[Depend('get_a')] + #[Depend('get_b')] public function test_c($a, $b) { $this->assertSame('a', $a); $this->assertSame('b', $b); } - /** - * @Depend("get_a") - * @Depend("get_b") - * @Depend("Asynit\Tests\AnotherTest::get_d") - */ + #[Depend('get_a')] + #[Depend('get_b')] + #[Depend("Asynit\Tests\AnotherTest::get_d")] public function test_c_with_d($a, $b, $d) { $this->assertSame('a', $a); diff --git a/tests/HttpClientOverrideTest.php b/tests/HttpClientOverrideTest.php deleted file mode 100644 index c9cd536..0000000 --- a/tests/HttpClientOverrideTest.php +++ /dev/null @@ -1,29 +0,0 @@ -createUri('http://127.0.0.1:8081'); - - return new PluginClient($asyncClient, [ - new BaseUriPlugin($uri), - ]); - } - - public function testFoo2() - { - $response = yield $this->get('/delay/3'); - - $this->assertInstanceOf(ResponseInterface::class, $response); - } -} diff --git a/tests/HttpClientOverrideTestPromise.php b/tests/HttpClientOverrideTestPromise.php deleted file mode 100644 index e500e5b..0000000 --- a/tests/HttpClientOverrideTestPromise.php +++ /dev/null @@ -1,35 +0,0 @@ -createUri('http://127.0.0.1:8081'); - $client = new PluginClient($asyncClient, [ - new BaseUriPlugin($uri), - ]); - - $request = new Request('GET', '/delay/1'); - - yield $client->sendAsyncRequest($request); - - return $client; - } - - public function testFoo2() - { - $response = yield $this->get('/delay/3'); - - $this->assertInstanceOf(ResponseInterface::class, $response); - } -} diff --git a/tests/Simple.php b/tests/Simple.php new file mode 100644 index 0000000..c717853 --- /dev/null +++ b/tests/Simple.php @@ -0,0 +1,41 @@ +