From b4bd49ddae00263b461d38b8884c15684f5d08ac Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sat, 25 May 2024 22:44:59 +0200 Subject: [PATCH 1/5] Rewrite for new version of workshop --- .docker/runtime/Dockerfile | 14 ++ .docker/runtime/docker-compose.yml | 14 ++ .github/workflows/build.yml | 5 +- app/config.php | 7 +- composer.json | 3 +- composer.lock | 145 +++++++++--------- phpunit.xml | 3 + src/Exercise/ArrayWeGo.php | 43 ++---- src/Exercise/BabySteps.php | 10 +- src/Exercise/ConcernedAboutSeparation.php | 44 ++---- src/Exercise/DatabaseRead.php | 25 +-- src/Exercise/DependencyHeaven.php | 22 ++- src/Exercise/ExceptionalCoding.php | 45 ++---- src/Exercise/FilteredLs.php | 36 ++--- src/Exercise/HelloWorld.php | 8 +- src/Exercise/HttpJsonApi.php | 18 ++- src/Exercise/MyFirstIo.php | 31 ++-- src/Exercise/TimeServer.php | 13 +- test/Exercise/ArrayWeGoTest.php | 100 +++++------- test/Exercise/BabyStepsTest.php | 49 ++++-- .../Exercise/ConcernedAboutSeparationTest.php | 100 +++--------- test/Exercise/DatabaseReadTest.php | 78 +++++++--- test/Exercise/DependencyHeavenTest.php | 65 +------- test/Exercise/ExceptionalCodingTest.php | 102 +++++------- test/Exercise/FilteredLsTest.php | 74 ++++----- test/Exercise/HelloWorldTest.php | 45 +++++- test/Exercise/HttpJsonApiTest.php | 54 +++++-- test/Exercise/MyFirstIoTest.php | 99 ++++++------ test/Exercise/TimeServerTest.php | 137 ++++++----------- .../concerned-about-separation/include.php | 3 - .../concerned-about-separation/no-include.php | 3 - .../array-we-go/no-required-functions.php | 8 + .../array-we-go/solution-correct.php | 13 ++ .../array-we-go/solution-no-code.php} | 0 .../array-we-go/solution-wrong-output.php | 12 ++ .../solutions/baby-steps/solution-correct.php | 8 + .../solutions/baby-steps/solution-no-code.php | 1 + .../baby-steps/solution-wrong-output.php | 3 + .../correct/DirectoryFilter.php | 19 +++ .../correct/solution.php | 7 + .../concerned-about-separation/no-include.php | 23 +++ .../solution-no-code.php | 1 + .../database-read/solution-correct.php | 10 ++ .../database-read/solution-no-code.php | 1 + .../database-read/solution-wrong-output.php | 10 ++ .../database-read/solution-wrong-update.php | 10 ++ .../no-composer/solution.php | 4 +- .../solution-banned-functions.php | 12 ++ .../exceptional-coding/solution-correct.php | 13 ++ .../exceptional-coding/solution-no-code.php | 1 + .../solution-wrong-output.php | 8 + .../filtered-ls/solution-correct.php | 7 + .../filtered-ls/solution-no-code.php | 1 + .../filtered-ls/solution-wrong-output.php | 5 + .../hello-world/solution-correct.php | 3 + .../hello-world/solution-no-code.php | 1 + .../hello-world/solution-wrong-output.php | 3 + .../http-json-api/solution-correct.php | 26 ++++ .../http-json-api/solution-no-code.php | 1 + .../http-json-api/solution-wrong-output.php | 26 ++++ .../my-first-io/solution-correct.php | 4 + .../my-first-io/solution-no-code.php | 1 + .../my-first-io/solution-wrong-output.php | 4 + .../wrong-function-requirements.php | 3 + .../time-server/solution-correct.php} | 0 .../time-server/solution-no-code.php | 1 + .../time-server/solution-no-server.php | 4 + .../time-server/solution-wrong-output.php} | 0 68 files changed, 870 insertions(+), 779 deletions(-) create mode 100644 .docker/runtime/Dockerfile create mode 100644 .docker/runtime/docker-compose.yml delete mode 100644 test/res/concerned-about-separation/include.php delete mode 100644 test/res/concerned-about-separation/no-include.php create mode 100644 test/solutions/array-we-go/no-required-functions.php create mode 100644 test/solutions/array-we-go/solution-correct.php rename test/{res/time-server/no-server.php => solutions/array-we-go/solution-no-code.php} (100%) create mode 100644 test/solutions/array-we-go/solution-wrong-output.php create mode 100644 test/solutions/baby-steps/solution-correct.php create mode 100644 test/solutions/baby-steps/solution-no-code.php create mode 100644 test/solutions/baby-steps/solution-wrong-output.php create mode 100644 test/solutions/concerned-about-separation/correct/DirectoryFilter.php create mode 100644 test/solutions/concerned-about-separation/correct/solution.php create mode 100644 test/solutions/concerned-about-separation/no-include.php create mode 100644 test/solutions/concerned-about-separation/solution-no-code.php create mode 100644 test/solutions/database-read/solution-correct.php create mode 100644 test/solutions/database-read/solution-no-code.php create mode 100644 test/solutions/database-read/solution-wrong-output.php create mode 100644 test/solutions/database-read/solution-wrong-update.php create mode 100644 test/solutions/exceptional-coding/solution-banned-functions.php create mode 100644 test/solutions/exceptional-coding/solution-correct.php create mode 100644 test/solutions/exceptional-coding/solution-no-code.php create mode 100644 test/solutions/exceptional-coding/solution-wrong-output.php create mode 100644 test/solutions/filtered-ls/solution-correct.php create mode 100644 test/solutions/filtered-ls/solution-no-code.php create mode 100644 test/solutions/filtered-ls/solution-wrong-output.php create mode 100644 test/solutions/hello-world/solution-correct.php create mode 100644 test/solutions/hello-world/solution-no-code.php create mode 100644 test/solutions/hello-world/solution-wrong-output.php create mode 100644 test/solutions/http-json-api/solution-correct.php create mode 100644 test/solutions/http-json-api/solution-no-code.php create mode 100644 test/solutions/http-json-api/solution-wrong-output.php create mode 100644 test/solutions/my-first-io/solution-correct.php create mode 100644 test/solutions/my-first-io/solution-no-code.php create mode 100644 test/solutions/my-first-io/solution-wrong-output.php create mode 100644 test/solutions/my-first-io/wrong-function-requirements.php rename test/{res/time-server/solution.php => solutions/time-server/solution-correct.php} (100%) create mode 100644 test/solutions/time-server/solution-no-code.php create mode 100644 test/solutions/time-server/solution-no-server.php rename test/{res/time-server/solution-wrong.php => solutions/time-server/solution-wrong-output.php} (100%) diff --git a/.docker/runtime/Dockerfile b/.docker/runtime/Dockerfile new file mode 100644 index 0000000..dc8e93f --- /dev/null +++ b/.docker/runtime/Dockerfile @@ -0,0 +1,14 @@ +FROM php:8.3-alpine as build + +RUN apk add --no-cache $PHPIZE_DEPS && \ + apk add --no-cache linux-headers + +RUN docker-php-ext-install sockets + +FROM php:8.3-alpine as final + +COPY --from=build /usr/local/lib/php /usr/local/lib/php +COPY --from=build /usr/local/etc/php /usr/local/etc/php + +RUN apk add git +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin --filename=composer \ No newline at end of file diff --git a/.docker/runtime/docker-compose.yml b/.docker/runtime/docker-compose.yml new file mode 100644 index 0000000..b38f58a --- /dev/null +++ b/.docker/runtime/docker-compose.yml @@ -0,0 +1,14 @@ +services: + runtime: + image: php8appreciate-runtime + build: + context: . + dockerfile: Dockerfile + + user: ${UID}:${GID} + + volumes: + - type: bind + source: ${SOLUTION} + target: '/solution' + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8075bce..9a24134 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,8 +32,9 @@ jobs: - name: Run phpunit tests run: | mkdir -p build/logs - vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml - + PROCESS_FACTORY=host vendor/bin/phpunit + PROCESS_FACTORY=docker vendor/bin/phpunit --coverage-clover ./build/logs/clover.xml + - name: Run phpcs run: composer cs diff --git a/app/config.php b/app/config.php index d4bb344..35e687d 100644 --- a/app/config.php +++ b/app/config.php @@ -28,22 +28,21 @@ HelloWorld::class => create(HelloWorld::class), HttpJsonApi::class => create(HttpJsonApi::class), MyFirstIo::class => factory(function (ContainerInterface $c) { - return new MyFirstIo($c->get(Filesystem::class), FakerFactory::create()); + return new MyFirstIo(FakerFactory::create()); }), FilteredLs::class => factory(function (ContainerInterface $c) { return new FilteredLs($c->get(Filesystem::class)); }), ConcernedAboutSeparation::class => factory(function (ContainerInterface $c) { return new ConcernedAboutSeparation( - $c->get(Filesystem::class), $c->get(Parser::class) ); }), ArrayWeGo::class => factory(function (ContainerInterface $c) { - return new ArrayWeGo($c->get(Filesystem::class), FakerFactory::create()); + return new ArrayWeGo(FakerFactory::create()); }), ExceptionalCoding::class => factory(function (ContainerInterface $c) { - return new ExceptionalCoding($c->get(Filesystem::class), FakerFactory::create()); + return new ExceptionalCoding(FakerFactory::create()); }), DatabaseRead::class => factory(function (ContainerInterface $c) { return new DatabaseRead(FakerFactory::create()); diff --git a/composer.json b/composer.json index a77c12e..f09b583 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,9 @@ ], "require" : { "php" : ">=8.0", + "ext-pdo": "*", "ext-pdo_sqlite": "*", - "php-school/php-workshop": "dev-master", + "php-school/php-workshop": "dev-docker-fixes", "ext-sockets": "*" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 4ed65f1..e6c5380 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c8283c629d2b2713b55e47ae1c37648f", + "content-hash": "205aa5b5714933bfd2300ccbfdadbd28", "packages": [ { "name": "beberlei/assert", @@ -734,21 +734,21 @@ }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", @@ -784,9 +784,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "php-di/invoker", @@ -1126,16 +1126,16 @@ }, { "name": "php-school/php-workshop", - "version": "dev-master", + "version": "dev-docker-fixes", "source": { "type": "git", "url": "https://github.com/php-school/php-workshop.git", - "reference": "11911b2157e80480a8cc272b56f417f5ba4903bf" + "reference": "065947216d954289c70826afe27ccf0b6f49f109" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-school/php-workshop/zipball/11911b2157e80480a8cc272b56f417f5ba4903bf", - "reference": "11911b2157e80480a8cc272b56f417f5ba4903bf", + "url": "https://api.github.com/repos/php-school/php-workshop/zipball/065947216d954289c70826afe27ccf0b6f49f109", + "reference": "065947216d954289c70826afe27ccf0b6f49f109", "shasum": "" }, "require": { @@ -1158,15 +1158,20 @@ "symfony/process": "^4.0 | ^5.0 | ^6.0" }, "require-dev": { + "bamarni/composer-bin-plugin": "^1.8", "composer/composer": "^2.0", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.8", "phpunit/phpunit": "^8.5", - "squizlabs/php_codesniffer": "^3.7", "yoast/phpunit-polyfills": "^0.2.0" }, - "default-branch": true, "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, "autoload": { "files": [ "src/Event/functions.php", @@ -1204,9 +1209,9 @@ ], "support": { "issues": "https://github.com/php-school/php-workshop/issues", - "source": "https://github.com/php-school/php-workshop/tree/master" + "source": "https://github.com/php-school/php-workshop/tree/docker-fixes" }, - "time": "2024-03-10T13:04:10+00:00" + "time": "2024-05-25T13:59:57+00:00" }, { "name": "php-school/terminal", @@ -1367,20 +1372,20 @@ }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -1404,7 +1409,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -1416,9 +1421,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + "source": "https://github.com/php-fig/http-factory" }, - "time": "2023-04-10T20:10:41+00:00" + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -1569,16 +1574,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "version": "v3.5.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", "shasum": "" }, "require": { @@ -1587,7 +1592,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "3.4-dev" + "dev-main": "3.5-dev" }, "thanks": { "name": "symfony/contracts", @@ -1616,7 +1621,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" }, "funding": [ { @@ -1632,26 +1637,27 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-04-18T09:32:20+00:00" }, { "name": "symfony/filesystem", - "version": "v6.4.3", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb" + "reference": "78dde75f8f6dbbca4ec436a4b0087f7af02076d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/78dde75f8f6dbbca4ec436a4b0087f7af02076d4", + "reference": "78dde75f8f6dbbca4ec436a4b0087f7af02076d4", "shasum": "" }, "require": { "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" + "symfony/polyfill-mbstring": "~1.8", + "symfony/process": "^5.4|^6.4" }, "type": "library", "autoload": { @@ -1679,7 +1685,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.3" + "source": "https://github.com/symfony/filesystem/tree/v6.4.7" }, "funding": [ { @@ -1695,7 +1701,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-04-18T09:22:46+00:00" }, { "name": "symfony/polyfill-ctype", @@ -1858,16 +1864,16 @@ }, { "name": "symfony/process", - "version": "v6.4.4", + "version": "v6.4.7", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "710e27879e9be3395de2b98da3f52a946039f297" + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297", - "reference": "710e27879e9be3395de2b98da3f52a946039f297", + "url": "https://api.github.com/repos/symfony/process/zipball/cdb1c81c145fd5aa9b0038bab694035020943381", + "reference": "cdb1c81c145fd5aa9b0038bab694035020943381", "shasum": "" }, "require": { @@ -1899,7 +1905,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.4" + "source": "https://github.com/symfony/process/tree/v6.4.7" }, "funding": [ { @@ -1915,7 +1921,7 @@ "type": "tidelift" } ], - "time": "2024-02-20T12:31:00+00:00" + "time": "2024-04-18T09:22:46+00:00" } ], "packages-dev": [ @@ -2168,16 +2174,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.60", + "version": "1.11.2", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe" + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/95dcea7d6c628a3f2f56d091d8a0219485a86bbe", - "reference": "95dcea7d6c628a3f2f56d091d8a0219485a86bbe", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0d5d4294a70deb7547db655c47685d680e39cfec", + "reference": "0d5d4294a70deb7547db655c47685d680e39cfec", "shasum": "" }, "require": { @@ -2220,13 +2226,9 @@ { "url": "https://github.com/phpstan", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" } ], - "time": "2024-03-07T13:30:19+00:00" + "time": "2024-05-24T13:23:04+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2549,16 +2551,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.17", + "version": "9.6.19", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd" + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1a156980d78a6666721b7e8e8502fe210b587fcd", - "reference": "1a156980d78a6666721b7e8e8502fe210b587fcd", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8", + "reference": "a1a54a473501ef4cdeaae4e06891674114d79db8", "shasum": "" }, "require": { @@ -2632,7 +2634,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19" }, "funding": [ { @@ -2648,7 +2650,7 @@ "type": "tidelift" } ], - "time": "2024-02-23T13:14:51+00:00" + "time": "2024-04-05T04:35:58+00:00" }, { "name": "sebastian/cli-parser", @@ -3452,16 +3454,16 @@ }, { "name": "sebastian/resource-operations", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e", + "reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e", "shasum": "" }, "require": { @@ -3473,7 +3475,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -3494,8 +3496,7 @@ "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4" }, "funding": [ { @@ -3503,7 +3504,7 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-03-14T16:00:52+00:00" }, { "name": "sebastian/type", @@ -3616,16 +3617,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.10.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/8f90f7a53ce271935282967f53d0894f8f1ff877", + "reference": "8f90f7a53ce271935282967f53d0894f8f1ff877", "shasum": "" }, "require": { @@ -3692,7 +3693,7 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" + "time": "2024-05-22T21:24:41+00:00" }, { "name": "theseer/tokenizer", diff --git a/phpunit.xml b/phpunit.xml index 4029e8b..760aa23 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,4 +10,7 @@ ./test ./test/solutions + + + diff --git a/src/Exercise/ArrayWeGo.php b/src/Exercise/ArrayWeGo.php index 53924d2..26172a5 100644 --- a/src/Exercise/ArrayWeGo.php +++ b/src/Exercise/ArrayWeGo.php @@ -8,23 +8,16 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\Exercise\TemporaryDirectoryTrait; use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck; -use PhpSchool\PhpWorkshop\ExerciseCheck\StdOutExerciseCheck; use PhpSchool\PhpWorkshop\ExerciseDispatcher; use Symfony\Component\Filesystem\Filesystem; class ArrayWeGo extends AbstractExercise implements ExerciseInterface, FunctionRequirementsExerciseCheck, CliExercise { - use TemporaryDirectoryTrait; - - private Filesystem $filesystem; - private Generator $faker; - - public function __construct(Filesystem $filesystem, Generator $faker) + public function __construct(private Generator $faker) { - $this->filesystem = $filesystem; - $this->faker = $faker; } public function getName(): string @@ -37,31 +30,29 @@ public function getDescription(): string return 'Filter an array of file paths and map to SplFile objects'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - $this->filesystem->mkdir($this->getTemporaryPath()); - $fileCount = rand(2, 10); - $realFiles = rand(1, $fileCount - 1); + $realFileCount = rand(1, $fileCount - 1); $files = []; + $realFiles = []; foreach (range(1, $fileCount) as $index) { - $file = sprintf('%s/%s.txt', $this->getTemporaryPath(), $this->faker->uuid()); - if ($index <= $realFiles) { - $this->filesystem->touch($file); + $file = $this->faker->uuid() . ".txt"; + if ($index <= $realFileCount) { + $realFiles[] = $file; } $files[] = $file; } - return [$files]; - } + $scenario = (new CliScenario()) + ->withExecution($files); - public function tearDown(): void - { - $this->filesystem->remove($this->getTemporaryPath()); + foreach ($realFiles as $realFile) { + $scenario->withFile($realFile, ''); + } + + return $scenario; } /** @@ -85,8 +76,8 @@ public function getType(): ExerciseType return new ExerciseType(ExerciseType::CLI); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array { - $dispatcher->requireCheck(FunctionRequirementsCheck::class); + return [FunctionRequirementsCheck::class]; } } diff --git a/src/Exercise/BabySteps.php b/src/Exercise/BabySteps.php index c0a1b6e..1b608ea 100644 --- a/src/Exercise/BabySteps.php +++ b/src/Exercise/BabySteps.php @@ -6,7 +6,7 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\ExerciseCheck\StdOutExerciseCheck; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; class BabySteps extends AbstractExercise implements ExerciseInterface, CliExercise { @@ -20,10 +20,7 @@ public function getDescription(): string return 'Simple Addition'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { $numArgs = rand(0, 10); @@ -32,7 +29,8 @@ public function getArgs(): array $args[] = (string) rand(0, 100); } - return [$args]; + return (new CliScenario()) + ->withExecution($args); } public function getType(): ExerciseType diff --git a/src/Exercise/ConcernedAboutSeparation.php b/src/Exercise/ConcernedAboutSeparation.php index 985e5a7..21facc3 100644 --- a/src/Exercise/ConcernedAboutSeparation.php +++ b/src/Exercise/ConcernedAboutSeparation.php @@ -8,28 +8,20 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Exercise\TemporaryDirectoryTrait; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\ExerciseCheck\SelfCheck; -use PhpSchool\PhpWorkshop\Input\Input; +use PhpSchool\PhpWorkshop\ExerciseRunner\Context\ExecutionContext; use PhpSchool\PhpWorkshop\Result\Failure; use PhpSchool\PhpWorkshop\Result\ResultInterface; use PhpSchool\PhpWorkshop\Result\Success; use PhpSchool\PhpWorkshop\Solution\DirectorySolution; use PhpSchool\PhpWorkshop\Solution\SolutionInterface; -use Symfony\Component\Filesystem\Filesystem; use PhpParser\Node\Expr\Include_; class ConcernedAboutSeparation extends AbstractExercise implements ExerciseInterface, CliExercise, SelfCheck { - use TemporaryDirectoryTrait; - - private Filesystem $filesystem; - private Parser $parser; - - public function __construct(Filesystem $filesystem, Parser $parser) + public function __construct(private Parser $parser) { - $this->filesystem = $filesystem; - $this->parser = $parser; } public function getName(): string @@ -42,13 +34,8 @@ public function getDescription(): string return 'Separate code and utilise files and classes'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - $folder = $this->getTemporaryPath(); - $files = [ "learnyouphp.dat", "learnyouphp.txt", @@ -71,18 +58,20 @@ public function getArgs(): array "dat", ]; - $this->filesystem->mkdir($folder); - array_walk($files, function ($file) use ($folder) { - $this->filesystem->dumpFile(sprintf('%s/%s', $folder, $file), ''); - }); - $ext = ''; while ($ext === '') { $index = array_rand($files); $ext = pathinfo($files[$index], PATHINFO_EXTENSION); } - return [[$folder, $ext]]; + $scenario = (new CliScenario()) + ->withExecution(['files', $ext]); + + array_walk($files, function (string $file) use ($scenario) { + $scenario->withFile('files/' . $file, ''); + }); + + return $scenario; } public function getSolution(): SolutionInterface @@ -90,14 +79,9 @@ public function getSolution(): SolutionInterface return DirectorySolution::fromDirectory(__DIR__ . '/../../exercises/concerned-about-separation/solution'); } - public function tearDown(): void - { - $this->filesystem->remove($this->getTemporaryPath()); - } - - public function check(Input $input): ResultInterface + public function check(ExecutionContext $context): ResultInterface { - $statements = $this->parser->parse((string) file_get_contents($input->getRequiredArgument('program'))); + $statements = $this->parser->parse((string) file_get_contents($context->getEntryPoint())); if (null === $statements) { return Failure::fromNameAndReason($this->getName(), 'No code was found'); diff --git a/src/Exercise/DatabaseRead.php b/src/Exercise/DatabaseRead.php index 36fc3b6..063f0af 100644 --- a/src/Exercise/DatabaseRead.php +++ b/src/Exercise/DatabaseRead.php @@ -5,29 +5,22 @@ use Faker\Generator; use PDO; use PhpSchool\PhpWorkshop\Check\DatabaseCheck; -use PhpSchool\PhpWorkshop\Check\ListenableCheckInterface; -use PhpSchool\PhpWorkshop\Event\Event; -use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exercise\AbstractExercise; use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\ExerciseCheck\DatabaseExerciseCheck; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use Symfony\Component\Filesystem\Filesystem; class DatabaseRead extends AbstractExercise implements ExerciseInterface, DatabaseExerciseCheck, CliExercise { - private Generator $faker; - /** * @var array{id: int, name: string} */ private array $randomRecord; - public function __construct(Generator $faker) + public function __construct(private Generator $faker) { - $this->faker = $faker; } public function getName(): string @@ -40,12 +33,10 @@ public function getDescription(): string return 'Read an SQL databases contents'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - return [[$this->randomRecord['name']]]; + return (new CliScenario()) + ->withExecution([$this->randomRecord['name']]); } public function seed(PDO $db): void @@ -77,7 +68,7 @@ public function verify(PDO $db): bool $sql = 'SELECT name FROM users WHERE id = :id'; $stmt = $db->prepare($sql); - if ($stmt == false) { + if (!$stmt) { return false; } @@ -92,8 +83,8 @@ public function getType(): ExerciseType return new ExerciseType(ExerciseType::CLI); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array { - $dispatcher->requireCheck(DatabaseCheck::class); + return [DatabaseCheck::class]; } } diff --git a/src/Exercise/DependencyHeaven.php b/src/Exercise/DependencyHeaven.php index b53a74f..2c0240e 100644 --- a/src/Exercise/DependencyHeaven.php +++ b/src/Exercise/DependencyHeaven.php @@ -9,6 +9,7 @@ use PhpSchool\PhpWorkshop\Exercise\CgiExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CgiScenario; use PhpSchool\PhpWorkshop\ExerciseCheck\ComposerExerciseCheck; use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Solution\DirectorySolution; @@ -42,20 +43,15 @@ public function getSolution(): SolutionInterface return DirectorySolution::fromDirectory(__DIR__ . '/../../exercises/dependency-heaven/solution'); } - /** - * @inheritdoc - */ - public function getRequests(): array + public function defineTestScenario(): CgiScenario { - $requests = []; - + $scenario = new CgiScenario(); for ($i = 0; $i < rand(2, 5); $i++) { - $requests[] = $this->newApiRequest('/reverse'); - $requests[] = $this->newApiRequest('/snake'); - $requests[] = $this->newApiRequest('/titleize'); + $scenario->withExecution($this->newApiRequest('/reverse')); + $scenario->withExecution($this->newApiRequest('/snake')); + $scenario->withExecution($this->newApiRequest('/titleize')); } - - return $requests; + return $scenario; } private function newApiRequest(string $endpoint): RequestInterface @@ -88,8 +84,8 @@ public function getType(): ExerciseType return new ExerciseType(ExerciseType::CGI); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array { - $dispatcher->requireCheck(ComposerCheck::class); + return [ComposerCheck::class]; } } diff --git a/src/Exercise/ExceptionalCoding.php b/src/Exercise/ExceptionalCoding.php index d8ade58..afea832 100644 --- a/src/Exercise/ExceptionalCoding.php +++ b/src/Exercise/ExceptionalCoding.php @@ -8,25 +8,16 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Exercise\TemporaryDirectoryTrait; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use Symfony\Component\Filesystem\Filesystem; class ExceptionalCoding extends AbstractExercise implements ExerciseInterface, CliExercise, FunctionRequirementsExerciseCheck { - use TemporaryDirectoryTrait; - - private Filesystem $filesystem; - private Generator $faker; - - public function __construct(Filesystem $filesystem, Generator $faker) + public function __construct(private Generator $faker) { - $this->filesystem = $filesystem; - $this->faker = $faker; } public function getName(): string @@ -39,31 +30,29 @@ public function getDescription(): string return "Introduction to Exceptions"; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - $this->filesystem->mkdir($this->getTemporaryPath()); - $fileCount = rand(2, 10); - $realFiles = rand(1, $fileCount - 1); + $realFileCount = rand(1, $fileCount - 1); $files = []; + $realFiles = []; foreach (range(1, $fileCount) as $index) { - $file = sprintf('%s/%s.txt', $this->getTemporaryPath(), $this->faker->uuid()); - if ($index <= $realFiles) { - $this->filesystem->touch($file); + $file = $this->faker->uuid() . ".txt"; + if ($index <= $realFileCount) { + $realFiles[] = $file; } $files[] = $file; } - return [$files]; - } + $scenario = (new CliScenario()) + ->withExecution($files); - public function tearDown(): void - { - $this->filesystem->remove($this->getTemporaryPath()); + foreach ($realFiles as $realFile) { + $scenario->withFile($realFile, ''); + } + + return $scenario; } /** @@ -87,8 +76,8 @@ public function getType(): ExerciseType return new ExerciseType(ExerciseType::CLI); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array { - $dispatcher->requireCheck(FunctionRequirementsCheck::class); + return [FunctionRequirementsCheck::class]; } } diff --git a/src/Exercise/FilteredLs.php b/src/Exercise/FilteredLs.php index 9a1bc1b..005280a 100644 --- a/src/Exercise/FilteredLs.php +++ b/src/Exercise/FilteredLs.php @@ -6,20 +6,10 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Exercise\TemporaryDirectoryTrait; -use Symfony\Component\Filesystem\Filesystem; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; class FilteredLs extends AbstractExercise implements ExerciseInterface, CliExercise { - use TemporaryDirectoryTrait; - - private Filesystem $filesystem; - - public function __construct(Filesystem $filesystem) - { - $this->filesystem = $filesystem; - } - public function getName(): string { return 'Filtered LS'; @@ -30,13 +20,8 @@ public function getDescription(): string return 'Read files in a folder and filter by a given extension'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - $folder = $this->getTemporaryPath(); - $files = [ "learnyouphp.dat", "learnyouphp.txt", @@ -59,23 +44,20 @@ public function getArgs(): array "dat", ]; - $this->filesystem->mkdir($folder); - array_walk($files, function ($file) use ($folder) { - $this->filesystem->dumpFile(sprintf('%s/%s', $folder, $file), ''); - }); - $ext = ''; while ($ext === '') { $index = array_rand($files); $ext = pathinfo($files[$index], PATHINFO_EXTENSION); } - return [[$folder, $ext]]; - } + $scenario = (new CliScenario()) + ->withExecution(['files', $ext]); - public function tearDown(): void - { - $this->filesystem->remove($this->getTemporaryPath()); + array_walk($files, function (string $file) use ($scenario) { + $scenario->withFile('files/' . $file, ''); + }); + + return $scenario; } public function getType(): ExerciseType diff --git a/src/Exercise/HelloWorld.php b/src/Exercise/HelloWorld.php index c94c575..bbd7435 100644 --- a/src/Exercise/HelloWorld.php +++ b/src/Exercise/HelloWorld.php @@ -6,6 +6,7 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; class HelloWorld extends AbstractExercise implements ExerciseInterface, CliExercise { @@ -19,12 +20,9 @@ public function getDescription(): string return 'Simple Hello World exercise'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - return [[]]; + return (new CliScenario())->withExecution(); } public function getType(): ExerciseType diff --git a/src/Exercise/HttpJsonApi.php b/src/Exercise/HttpJsonApi.php index 34bdffb..8167372 100644 --- a/src/Exercise/HttpJsonApi.php +++ b/src/Exercise/HttpJsonApi.php @@ -7,6 +7,7 @@ use PhpSchool\PhpWorkshop\Exercise\CgiExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CgiScenario; use Psr\Http\Message\RequestInterface; class HttpJsonApi extends AbstractExercise implements ExerciseInterface, CgiExercise @@ -21,16 +22,17 @@ public function getDescription(): string return 'HTTP JSON API - Servers JSON when it receives a GET request'; } - /** - * @inheritdoc - */ - public function getRequests(): array + public function defineTestScenario(): CgiScenario { $url = 'http://www.time.com/api/%s?iso=%s'; - return [ - (new Request('GET', sprintf($url, 'parsetime', urlencode((new \DateTime())->format(DATE_ISO8601))))), - (new Request('GET', sprintf($url, 'unixtime', urlencode((new \DateTime())->format(DATE_ISO8601))))) - ]; + + return (new CgiScenario()) + ->withExecution( + new Request('GET', sprintf($url, 'parsetime', urlencode((new \DateTime())->format(DATE_ISO8601)))) + ) + ->withExecution( + new Request('GET', sprintf($url, 'unixtime', urlencode((new \DateTime())->format(DATE_ISO8601)))) + ); } public function getType(): ExerciseType diff --git a/src/Exercise/MyFirstIo.php b/src/Exercise/MyFirstIo.php index a40f82c..bed0590 100644 --- a/src/Exercise/MyFirstIo.php +++ b/src/Exercise/MyFirstIo.php @@ -8,6 +8,7 @@ use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\Exercise\TemporaryDirectoryTrait; use PhpSchool\PhpWorkshop\ExerciseCheck\FunctionRequirementsExerciseCheck; use PhpSchool\PhpWorkshop\ExerciseDispatcher; @@ -18,15 +19,8 @@ class MyFirstIo extends AbstractExercise implements CliExercise, FunctionRequirementsExerciseCheck { - use TemporaryDirectoryTrait; - - private Filesystem $filesystem; - private Generator $faker; - - public function __construct(Filesystem $filesystem, Generator $faker) + public function __construct(private Generator $faker) { - $this->filesystem = $filesystem; - $this->faker = $faker; } public function getName(): string @@ -39,21 +33,14 @@ public function getDescription(): string return 'Read a file from the file system'; } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - $path = $this->getTemporaryPath(); + $filename = bin2hex(random_bytes(4)) . '.txt'; $paragraphs = implode("\n\n", (array) $this->faker->paragraphs(rand(5, 50))); - $this->filesystem->dumpFile($path, $paragraphs); - - return [[$path]]; - } - public function tearDown(): void - { - $this->filesystem->remove($this->getTemporaryPath()); + return (new CliScenario()) + ->withExecution([$filename]) + ->withFile($filename, $paragraphs); } /** @@ -77,8 +64,8 @@ public function getType(): ExerciseType return new ExerciseType(ExerciseType::CLI); } - public function configure(ExerciseDispatcher $dispatcher): void + public function getRequiredChecks(): array { - $dispatcher->requireCheck(FunctionRequirementsCheck::class); + return [FunctionRequirementsCheck::class]; } } diff --git a/src/Exercise/TimeServer.php b/src/Exercise/TimeServer.php index df2be90..ae1dada 100644 --- a/src/Exercise/TimeServer.php +++ b/src/Exercise/TimeServer.php @@ -3,11 +3,13 @@ namespace PhpSchool\LearnYouPhp\Exercise; use PhpSchool\PhpWorkshop\Event\CliExecuteEvent; +use PhpSchool\PhpWorkshop\Event\EventDispatcher; use PhpSchool\PhpWorkshop\Exception\RuntimeException; use PhpSchool\PhpWorkshop\Exercise\AbstractExercise; use PhpSchool\PhpWorkshop\Exercise\CliExercise; use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\ComparisonFailure; @@ -27,10 +29,8 @@ public function getDescription(): string return 'Build a Time Server!'; } - public function configure(ExerciseDispatcher $exerciseDispatcher): void + public function defineListeners(EventDispatcher $eventDispatcher): void { - $eventDispatcher = $exerciseDispatcher->getEventDispatcher(); - $appendArgsListener = function (CliExecuteEvent $event) { $event->appendArg('127.0.0.1'); $event->appendArg($this->getRandomPort()); @@ -115,12 +115,9 @@ public function getType(): ExerciseType return new ExerciseType(ExerciseType::CLI); } - /** - * @inheritdoc - */ - public function getArgs(): array + public function defineTestScenario(): CliScenario { - return []; + return (new CliScenario())->withExecution(); } private function createSocket(): Socket diff --git a/test/Exercise/ArrayWeGoTest.php b/test/Exercise/ArrayWeGoTest.php index 8653942..82a58da 100644 --- a/test/Exercise/ArrayWeGoTest.php +++ b/test/Exercise/ArrayWeGoTest.php @@ -3,35 +3,28 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; use Faker\Factory; -use Faker\Generator; use PhpSchool\LearnYouPhp\Exercise\ArrayWeGo; -use PhpSchool\PhpWorkshop\Check\FunctionRequirementsCheck; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Filesystem\Filesystem; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\Result\FunctionRequirementsFailure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; -class ArrayWeGoTest extends TestCase +class ArrayWeGoTest extends WorkshopExerciseTest { - /** - * @var Generator - */ - private $faker; - - /** - * @var Filesystem - */ - private $filesystem; + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } - public function setUp(): void + public function getExerciseClass(): string { - $this->faker = Factory::create(); - $this->filesystem = new Filesystem(); + return ArrayWeGo::class; } - public function testArrWeGoExercise(): void + public function testExerciseMeta(): void { - $e = new ArrayWeGo($this->filesystem, $this->faker); + $e = new ArrayWeGo(Factory::create()); $this->assertEquals('Array We Go!', $e->getName()); $this->assertEquals('Filter an array of file paths and map to SplFile objects', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); @@ -39,69 +32,46 @@ public function testArrWeGoExercise(): void $this->assertFileExists(realpath($e->getProblem())); } - public function testGetArgsCreateAtLeastOneExistingFile(): void + public function testWithNoCode(): void { - $e = new ArrayWeGo($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; + $this->runExercise('solution-no-code.php'); - $existingFiles = array_filter($args, 'file_exists'); + $this->assertVerifyWasNotSuccessful(); - foreach ($existingFiles as $file) { - $this->assertFileExists($file); - } - - $this->assertGreaterThanOrEqual(1, count($existingFiles)); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); } - public function testGetArgsHasAtLeastOneNonExistingFile(): void + public function testWithIncorrectOutput(): void { - $e = new ArrayWeGo($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; - - $nonExistingFiles = array_filter($args, function ($arg) { - return !file_exists($arg); - }); + $this->runExercise('solution-wrong-output.php'); - foreach ($nonExistingFiles as $file) { - $this->assertFileDoesNotExist($file); - } + $this->assertVerifyWasNotSuccessful(); - $this->assertGreaterThanOrEqual(1, count($nonExistingFiles)); + $this->assertOutputWasIncorrect(); } - public function testTearDownRemovesFile(): void + public function testFailureWhenNotUsingRequiredFunctions(): void { - $e = new ArrayWeGo($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; + $this->runExercise('no-required-functions.php'); - $existingFiles = array_filter($args, 'file_exists'); + $this->assertVerifyWasNotSuccessful(); - $this->assertFileExists($existingFiles[0]); + $this->assertOutputWasCorrect(); - $e->tearDown(); + $this->assertResultsHasFailureAndMatches( + FunctionRequirementsFailure::class, + function (FunctionRequirementsFailure $failure) { + self::assertEquals(['array_shift', 'array_filter', 'array_map'], $failure->getMissingFunctions()); - $this->assertFileDoesNotExist($existingFiles[0]); + return true; + } + ); } - public function testFunctionRequirements(): void + public function testWithCorrectSolution(): void { - $e = new ArrayWeGo($this->filesystem, $this->faker); - $this->assertEquals(['array_shift', 'array_filter', 'array_map'], $e->getRequiredFunctions()); - $this->assertEquals(['basename'], $e->getBannedFunctions()); - } - - public function testConfigure(): void - { - $dispatcher = $this->getMockBuilder(ExerciseDispatcher::class) - ->disableOriginalConstructor() - ->getMock(); - - $dispatcher - ->expects($this->once()) - ->method('requireCheck') - ->with(FunctionRequirementsCheck::class); + $this->runExercise('solution-correct.php'); - $e = new ArrayWeGo($this->filesystem, $this->faker); - $e->configure($dispatcher); + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/BabyStepsTest.php b/test/Exercise/BabyStepsTest.php index 3e33e79..0e586ff 100644 --- a/test/Exercise/BabyStepsTest.php +++ b/test/Exercise/BabyStepsTest.php @@ -2,29 +2,56 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; use PHPUnit\Framework\TestCase; use PhpSchool\LearnYouPhp\Exercise\BabySteps; -class BabyStepsTest extends TestCase +class BabyStepsTest extends WorkshopExerciseTest { - public function testBabyStepsExercise(): void + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } + + public function getExerciseClass(): string + { + return BabySteps::class; + } + + public function testExerciseMeta(): void { $e = new BabySteps(); $this->assertEquals('Baby Steps', $e->getName()); $this->assertEquals('Simple Addition', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); + $this->assertFileExists(realpath($e->getProblem())); + } - //sometime we don't get any args as number of args is random - //we need some args for code-coverage, so just try again - do { - $args = $e->getArgs()[0]; - } while (empty($args)); + public function testWithNoCode(): void + { + $this->runExercise('solution-no-code.php'); - foreach ($args as $arg) { - $this->assertIsNumeric($arg); - } + $this->assertVerifyWasNotSuccessful(); - $this->assertFileExists(realpath($e->getProblem())); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); + } + + public function testWithIncorrectOutput(): void + { + $this->runExercise('solution-wrong-output.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertOutputWasIncorrect(); + } + + public function testWithCorrectSolution(): void + { + $this->runExercise('solution-correct.php'); + + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/ConcernedAboutSeparationTest.php b/test/Exercise/ConcernedAboutSeparationTest.php index 69616ed..be50d9b 100644 --- a/test/Exercise/ConcernedAboutSeparationTest.php +++ b/test/Exercise/ConcernedAboutSeparationTest.php @@ -2,37 +2,28 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; -use PhpParser\Parser; use PhpParser\ParserFactory; use PhpSchool\LearnYouPhp\Exercise\ConcernedAboutSeparation; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Input\Input; use PhpSchool\PhpWorkshop\Result\Failure; -use PhpSchool\PhpWorkshop\Result\Success; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Filesystem\Filesystem; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; -class ConcernedAboutSeparationTest extends TestCase +class ConcernedAboutSeparationTest extends WorkshopExerciseTest { - /** - * @var Filesystem - */ - private $filesystem; - - /** - * @var Parser - */ - private $parser; + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } - public function setUp(): void + public function getExerciseClass(): string { - $this->filesystem = new Filesystem(); - $this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); + return ConcernedAboutSeparation::class; } - public function testConcernedAboutSeparationExercise(): void + public function testExerciseMeta(): void { - $e = new ConcernedAboutSeparation($this->filesystem, $this->parser); + $e = new ConcernedAboutSeparation((new ParserFactory())->create(ParserFactory::PREFER_PHP7)); $this->assertEquals('Concerned about Separation?', $e->getName()); $this->assertEquals('Separate code and utilise files and classes', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); @@ -40,74 +31,31 @@ public function testConcernedAboutSeparationExercise(): void $this->assertFileExists(realpath($e->getProblem())); } - public function testGetArgsCreatesFilesAndReturnsRandomExt(): void + public function testWithNoCode(): void { - $e = new ConcernedAboutSeparation($this->filesystem, $this->parser); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); - - $files = [ - "learnyouphp.dat", - "learnyouphp.txt", - "learnyouphp.sql", - "api.html", - "README.md", - "CHANGELOG.md", - "LICENCE.md", - "md", - "data.json", - "data.dat", - "words.dat", - "w00t.dat", - "w00t.txt", - "wrrrrongdat", - "dat", - ]; - - array_walk($files, function ($file) use ($path) { - $this->assertFileExists(sprintf('%s/%s', $path, $file)); - }); + $this->runExercise('solution-no-code.php'); - $extensions = array_unique(array_map(function ($file) { - return pathinfo($file, PATHINFO_EXTENSION); - }, $files)); + $this->assertVerifyWasNotSuccessful(); - $this->assertContains($args[1], $extensions); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); } - public function testTearDownRemovesFile(): void + public function testFailureWhenNotUsingInclude(): void { - $e = new ConcernedAboutSeparation($this->filesystem, $this->parser); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); + $this->runExercise('no-include.php'); - $e->tearDown(); + $this->assertVerifyWasNotSuccessful(); - $this->assertFileDoesNotExist($path); - } - - public function testCheckReturnsFailureIfNoIncludeFoundInSolution(): void - { - $e = new ConcernedAboutSeparation($this->filesystem, $this->parser); - $failure = $e->check( - new Input('learnyouphp', ['program' => __DIR__ . '/../res/concerned-about-separation/no-include.php']) + $this->assertResultsHasFailure( + Failure::class, + 'No require statement found' ); - - $this->assertInstanceOf(Failure::class, $failure); - $this->assertEquals('No require statement found', $failure->getReason()); - $this->assertEquals('Concerned about Separation?', $failure->getCheckName()); } - public function testCheckReturnsSuccessIfIncludeFound(): void + public function testWithCorrectSolution(): void { - $e = new ConcernedAboutSeparation($this->filesystem, $this->parser); - $success = $e->check( - new Input('learnyouphp', ['program' => __DIR__ . '/../res/concerned-about-separation/include.php']) - ); + $this->runExercise('correct/solution.php', self::DIRECTORY_SOLUTION); - $this->assertInstanceOf(Success::class, $success); - $this->assertEquals('Concerned about Separation?', $success->getCheckName()); + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/DatabaseReadTest.php b/test/Exercise/DatabaseReadTest.php index 152a222..d571c57 100644 --- a/test/Exercise/DatabaseReadTest.php +++ b/test/Exercise/DatabaseReadTest.php @@ -6,22 +6,39 @@ use Faker\Generator; use PDO; use PhpSchool\LearnYouPhp\Exercise\DatabaseRead; -use PhpSchool\PhpWorkshop\Check\DatabaseCheck; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use PhpSchool\PhpWorkshop\Solution\SolutionInterface; -use PHPUnit\Framework\TestCase; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; -class DatabaseReadTest extends TestCase +class DatabaseReadTest extends WorkshopExerciseTest { - /** - * @var Generator - */ - private $faker; + private Generator $faker; public function setUp(): void { $this->faker = Factory::create(); + parent::setUp(); + } + + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } + + public function getExerciseClass(): string + { + return DatabaseRead::class; + } + + public function testExerciseMeta(): void + { + $e = new DatabaseRead($this->faker); + $this->assertEquals('Database Read', $e->getName()); + $this->assertEquals('Read an SQL databases contents', $e->getDescription()); + $this->assertEquals(ExerciseType::CLI, $e->getType()); + + $this->assertFileExists(realpath($e->getProblem())); } public function testDatabaseExercise(): void @@ -42,13 +59,13 @@ public function testSeedAddsRandomUsersToDatabaseAndStoresRandomIdAndName(): voi $e->seed($db); - $args = $e->getArgs()[0]; + $scenario = $e->defineTestScenario(); $stmt = $db->query('SELECT * FROM users;'); $users = $stmt->fetchAll(PDO::FETCH_ASSOC); $this->assertTrue(count($users) >= 5); $this->assertIsArray($users); - $this->assertContains($args[0], array_column($users, 'name')); + $this->assertContains($scenario->getExecutions()[0]->get(0), array_column($users, 'name')); } public function testVerifyReturnsTrueIfRecordExistsWithNameUsingStoredId(): void @@ -69,18 +86,37 @@ public function testVerifyReturnsTrueIfRecordExistsWithNameUsingStoredId(): void $this->assertTrue($e->verify($db)); } - public function testConfigure(): void + public function testWithNoCode(): void { - $dispatcher = $this->getMockBuilder(ExerciseDispatcher::class) - ->disableOriginalConstructor() - ->getMock(); + $this->runExercise('solution-no-code.php'); - $dispatcher - ->expects($this->once()) - ->method('requireCheck') - ->with(DatabaseCheck::class); + $this->assertVerifyWasNotSuccessful(); - $e = new DatabaseRead($this->faker); - $e->configure($dispatcher); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); + } + + public function testWithIncorrectOutput(): void + { + $this->runExercise('solution-wrong-output.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertOutputWasIncorrect(); + } + + public function testFailureWhenNameIsNotUpdated(): void + { + $this->runExercise('solution-wrong-update.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertResultsHasFailure(Failure::class, 'Database verification failed'); + } + + public function testWithCorrectSolution(): void + { + $this->runExercise('solution-correct.php'); + + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/DependencyHeavenTest.php b/test/Exercise/DependencyHeavenTest.php index 35efa56..eab3404 100644 --- a/test/Exercise/DependencyHeavenTest.php +++ b/test/Exercise/DependencyHeavenTest.php @@ -20,10 +20,7 @@ class DependencyHeavenTest extends WorkshopExerciseTest { - /** - * @var Generator - */ - private $faker; + private Generator $faker; public function setUp(): void { @@ -42,58 +39,6 @@ public function testDependencyHeavenExercise(): void $this->assertFileExists(realpath($e->getProblem())); } - public function testGetRequiredPackages(): void - { - $this->assertSame( - ['league/route', 'laminas/laminas-diactoros', 'laminas/laminas-httphandlerrunner', 'symfony/string'], - (new DependencyHeaven($this->faker))->getRequiredPackages() - ); - } - - public function testGetRequests(): void - { - $e = new DependencyHeaven($this->faker); - - $requests = $e->getRequests(); - - foreach ($requests as $request) { - $this->assertInstanceOf(RequestInterface::class, $request); - $this->assertSame('POST', $request->getMethod()); - $this->assertSame(['application/x-www-form-urlencoded'], $request->getHeader('Content-Type')); - $this->assertNotEmpty($request->getBody()); - } - } - - public function testGetRequestsReturnsMultipleRequestsForEachEndpoint(): void - { - $e = new DependencyHeaven($this->faker); - - $endPoints = array_map(function (RequestInterface $request) { - return $request->getUri()->getPath(); - }, $e->getRequests()); - - $counts = array_count_values($endPoints); - foreach (['/reverse', '/snake', '/titleize'] as $endPoint) { - $this->assertTrue(isset($counts[$endPoint])); - $this->assertGreaterThan(1, $counts[$endPoint]); - } - } - - public function testConfigure(): void - { - $dispatcher = $this->getMockBuilder(ExerciseDispatcher::class) - ->disableOriginalConstructor() - ->getMock(); - - $dispatcher - ->expects($this->once()) - ->method('requireCheck') - ->with(ComposerCheck::class); - - $e = new DependencyHeaven($this->faker); - $e->configure($dispatcher); - } - public function getExerciseClass(): string { return DependencyHeaven::class; @@ -106,7 +51,7 @@ public function getApplication(): Application public function testWithNoComposerFile(): void { - $this->runExercise('no-composer/solution.php'); + $this->runExercise('no-composer/solution.php', self::DIRECTORY_SOLUTION); $this->assertVerifyWasNotSuccessful(); $this->assertResultsHasFailureAndMatches( @@ -119,7 +64,7 @@ function (ComposerFailure $failure) { public function testWithNoCode(): void { - $this->runExercise('no-code/solution.php'); + $this->runExercise('no-code/solution.php', self::DIRECTORY_SOLUTION); $this->assertVerifyWasNotSuccessful(); @@ -128,7 +73,7 @@ public function testWithNoCode(): void public function testWithWrongEndpoint(): void { - $this->runExercise('wrong-endpoint/solution.php'); + $this->runExercise('wrong-endpoint/solution.php', self::DIRECTORY_SOLUTION); $this->assertVerifyWasNotSuccessful(); @@ -158,7 +103,7 @@ public function testWithWrongEndpoint(): void public function testWithCorrectSolution(): void { - $this->runExercise('correct-solution/solution.php'); + $this->runExercise('correct-solution/solution.php', self::DIRECTORY_SOLUTION); $this->assertVerifyWasSuccessful(); } diff --git a/test/Exercise/ExceptionalCodingTest.php b/test/Exercise/ExceptionalCodingTest.php index 39ad564..38fc9fe 100644 --- a/test/Exercise/ExceptionalCodingTest.php +++ b/test/Exercise/ExceptionalCodingTest.php @@ -5,33 +5,36 @@ use Faker\Factory; use Faker\Generator; use PhpSchool\LearnYouPhp\Exercise\ExceptionalCoding; -use PhpSchool\PhpWorkshop\Check\FunctionRequirementsCheck; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use PHPUnit\Framework\TestCase; -use Symfony\Component\Filesystem\Filesystem; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\Result\FunctionRequirementsFailure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; -class ExceptionalCodingTest extends TestCase +class ExceptionalCodingTest extends WorkshopExerciseTest { - /** - * @var Generator - */ - private $faker; + private Generator $faker; - /** - * @var Filesystem - */ - private $filesystem; public function setUp(): void { $this->faker = Factory::create(); - $this->filesystem = new Filesystem(); + parent::setUp(); } - public function testArrWeGoExercise(): void + public function getApplication(): Application { - $e = new ExceptionalCoding($this->filesystem, $this->faker); + return require __DIR__ . '/../../app/bootstrap.php'; + } + + public function getExerciseClass(): string + { + return ExceptionalCoding::class; + } + + public function testExerciseMeta(): void + { + $e = new ExceptionalCoding($this->faker); $this->assertEquals('Exceptional Coding', $e->getName()); $this->assertEquals('Introduction to Exceptions', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); @@ -39,69 +42,46 @@ public function testArrWeGoExercise(): void $this->assertFileExists(realpath($e->getProblem())); } - public function testGetArgsCreateAtleastOneExistingFile(): void + public function testWithNoCode(): void { - $e = new ExceptionalCoding($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; + $this->runExercise('solution-no-code.php'); - $existingFiles = array_filter($args, 'file_exists'); + $this->assertVerifyWasNotSuccessful(); - foreach ($existingFiles as $file) { - $this->assertFileExists($file); - } - - $this->assertGreaterThanOrEqual(1, count($existingFiles)); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); } - public function testGetArgsHasAtleastOneNonExistingFile(): void + public function testWithIncorrectOutput(): void { - $e = new ExceptionalCoding($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; - - $nonExistingFiles = array_filter($args, function ($arg) { - return !file_exists($arg); - }); + $this->runExercise('solution-wrong-output.php'); - foreach ($nonExistingFiles as $file) { - $this->assertFileDoesNotExist($file); - } + $this->assertVerifyWasNotSuccessful(); - $this->assertGreaterThanOrEqual(1, count($nonExistingFiles)); + $this->assertOutputWasIncorrect(); } - public function testTearDownRemovesFile(): void + public function testFailureWhenNotUsingRequiredFunctions(): void { - $e = new ExceptionalCoding($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; - - $existingFiles = array_filter($args, 'file_exists'); + $this->runExercise('solution-banned-functions.php'); - $this->assertFileExists($existingFiles[0]); + $this->assertVerifyWasNotSuccessful(); - $e->tearDown(); + $this->assertOutputWasCorrect(); - $this->assertFileDoesNotExist($existingFiles[0]); - } + $this->assertResultsHasFailureAndMatches( + FunctionRequirementsFailure::class, + function (FunctionRequirementsFailure $failure) { + self::assertEquals([['function' => 'file_exists', 'line' => 7]], $failure->getBannedFunctions()); - public function testFunctionRequirements(): void - { - $e = new ExceptionalCoding($this->filesystem, $this->faker); - $this->assertEquals([], $e->getRequiredFunctions()); - $this->assertEquals(['array_filter', 'file_exists'], $e->getBannedFunctions()); + return true; + } + ); } - public function testConfigure(): void + public function testWithCorrectSolution(): void { - $dispatcher = $this->getMockBuilder(ExerciseDispatcher::class) - ->disableOriginalConstructor() - ->getMock(); - - $dispatcher - ->expects($this->once()) - ->method('requireCheck') - ->with(FunctionRequirementsCheck::class); + $this->runExercise('solution-correct.php'); - $e = new ExceptionalCoding($this->filesystem, $this->faker); - $e->configure($dispatcher); + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/FilteredLsTest.php b/test/Exercise/FilteredLsTest.php index fee3b33..80df6d0 100644 --- a/test/Exercise/FilteredLsTest.php +++ b/test/Exercise/FilteredLsTest.php @@ -2,26 +2,27 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PHPUnit\Framework\TestCase; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; use PhpSchool\LearnYouPhp\Exercise\FilteredLs; -use Symfony\Component\Filesystem\Filesystem; -class FilteredLsTest extends TestCase +class FilteredLsTest extends WorkshopExerciseTest { - /** - * @var Filesystem - */ - private $filesystem; + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } - public function setUp(): void + public function getExerciseClass(): string { - $this->filesystem = new Filesystem(); + return FilteredLs::class; } - public function testFilteredLsExercise(): void + public function testExerciseMeta(): void { - $e = new FilteredLs($this->filesystem); + $e = new FilteredLs(); $this->assertEquals('Filtered LS', $e->getName()); $this->assertEquals('Read files in a folder and filter by a given extension', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); @@ -29,51 +30,28 @@ public function testFilteredLsExercise(): void $this->assertFileExists(realpath($e->getProblem())); } - public function testGetArgsCreatesFilesAndReturnsRandomExt(): void + public function testWithNoCode(): void { - $e = new FilteredLs($this->filesystem); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); + $this->runExercise('solution-no-code.php'); + + $this->assertVerifyWasNotSuccessful(); - $files = [ - "learnyouphp.dat", - "learnyouphp.txt", - "learnyouphp.sql", - "api.html", - "README.md", - "CHANGELOG.md", - "LICENCE.md", - "md", - "data.json", - "data.dat", - "words.dat", - "w00t.dat", - "w00t.txt", - "wrrrrongdat", - "dat", - ]; + $this->assertResultsHasFailure(Failure::class, 'No code was found'); + } - array_walk($files, function ($file) use ($path) { - $this->assertFileExists(sprintf('%s/%s', $path, $file)); - }); + public function testWithIncorrectOutput(): void + { + $this->runExercise('solution-wrong-output.php'); - $extensions = array_unique(array_map(function ($file) { - return pathinfo($file, PATHINFO_EXTENSION); - }, $files)); + $this->assertVerifyWasNotSuccessful(); - $this->assertContains($args[1], $extensions); + $this->assertOutputWasIncorrect(); } - public function testTearDownRemovesFile(): void + public function testWithCorrectSolution(): void { - $e = new FilteredLs($this->filesystem); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); - - $e->tearDown(); + $this->runExercise('solution-correct.php'); - $this->assertFileDoesNotExist($path); + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/HelloWorldTest.php b/test/Exercise/HelloWorldTest.php index b5e57e7..ad85d90 100644 --- a/test/Exercise/HelloWorldTest.php +++ b/test/Exercise/HelloWorldTest.php @@ -2,22 +2,55 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Solution\SolutionInterface; -use PHPUnit\Framework\TestCase; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; use PhpSchool\LearnYouPhp\Exercise\HelloWorld; -class HelloWorldTest extends TestCase +class HelloWorldTest extends WorkshopExerciseTest { - public function testHelloWorldExercise(): void + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } + + public function getExerciseClass(): string + { + return HelloWorld::class; + } + + public function testExerciseMeta(): void { $e = new HelloWorld(); $this->assertEquals('Hello World', $e->getName()); $this->assertEquals('Simple Hello World exercise', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); + $this->assertFileExists(realpath($e->getProblem())); + } - $this->assertEquals([], $e->getArgs()[0]); + public function testWithNoCode(): void + { + $this->runExercise('solution-no-code.php'); - $this->assertFileExists(realpath($e->getProblem())); + $this->assertVerifyWasNotSuccessful(); + + $this->assertResultsHasFailure(Failure::class, 'No code was found'); + } + + public function testWithIncorrectOutput(): void + { + $this->runExercise('solution-wrong-output.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertOutputWasIncorrect(); + } + + public function testWithCorrectSolution(): void + { + $this->runExercise('solution-correct.php'); + + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/HttpJsonApiTest.php b/test/Exercise/HttpJsonApiTest.php index b83dded..d3d2584 100644 --- a/test/Exercise/HttpJsonApiTest.php +++ b/test/Exercise/HttpJsonApiTest.php @@ -3,34 +3,56 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; use PhpSchool\LearnYouPhp\Exercise\HttpJsonApi; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\Solution\SolutionInterface; -use PHPUnit\Framework\TestCase; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; use Psr\Http\Message\RequestInterface; -class HttpJsonApiTest extends TestCase +class HttpJsonApiTest extends WorkshopExerciseTest { - public function testHttpJsonApiExercise(): void + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } + + public function getExerciseClass(): string + { + return HttpJsonApi::class; + } + + public function testExerciseMeta(): void { $e = new HttpJsonApi(); $this->assertEquals('HTTP JSON API', $e->getName()); $this->assertEquals('HTTP JSON API - Servers JSON when it receives a GET request', $e->getDescription()); $this->assertEquals(ExerciseType::CGI, $e->getType()); - $requests = $e->getRequests(); - $request1 = $requests[0]; - $request2 = $requests[1]; + $this->assertFileExists(realpath($e->getProblem())); + } - $this->assertInstanceOf(RequestInterface::class, $request1); - $this->assertInstanceOf(RequestInterface::class, $request2); + public function testWithNoCode(): void + { + $this->runExercise('solution-no-code.php'); - $this->assertSame('GET', $request1->getMethod()); - $this->assertSame('GET', $request2->getMethod()); - $this->assertSame('www.time.com', $request1->getUri()->getHost()); - $this->assertSame('www.time.com', $request2->getUri()->getHost()); - $this->assertSame('/api/parsetime', $request1->getUri()->getPath()); - $this->assertSame('/api/unixtime', $request2->getUri()->getPath()); + $this->assertVerifyWasNotSuccessful(); - $this->assertFileExists(realpath($e->getProblem())); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); + } + + public function testWithIncorrectOutput(): void + { + $this->runExercise('solution-wrong-output.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertOutputWasIncorrect(); + } + + public function testWithCorrectSolution(): void + { + $this->runExercise('solution-correct.php'); + + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/MyFirstIoTest.php b/test/Exercise/MyFirstIoTest.php index ab2a4d9..14b0804 100644 --- a/test/Exercise/MyFirstIoTest.php +++ b/test/Exercise/MyFirstIoTest.php @@ -4,35 +4,36 @@ use Faker\Factory; use Faker\Generator; -use PhpSchool\PhpWorkshop\Check\FunctionRequirementsCheck; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use PhpSchool\PhpWorkshop\Solution\SolutionInterface; -use PHPUnit\Framework\TestCase; +use PhpSchool\PhpWorkshop\Result\Failure; +use PhpSchool\PhpWorkshop\Result\FunctionRequirementsFailure; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; use PhpSchool\LearnYouPhp\Exercise\MyFirstIo; -use Symfony\Component\Filesystem\Filesystem; -class MyFirstIoTest extends TestCase +class MyFirstIoTest extends WorkshopExerciseTest { - /** - * @var Generator - */ - private $faker; - - /** - * @var Filesystem - */ - private $filesystem; + private Generator $faker; public function setUp(): void { $this->faker = Factory::create(); - $this->filesystem = new Filesystem(); + parent::setUp(); + } + + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } + + public function getExerciseClass(): string + { + return MyFirstIo::class; } - public function testMyFirstIoExercise(): void + public function testExerciseMeta(): void { - $e = new MyFirstIo($this->filesystem, $this->faker); + $e = new MyFirstIo($this->faker); $this->assertEquals('My First IO', $e->getName()); $this->assertEquals('Read a file from the file system', $e->getDescription()); $this->assertEquals(ExerciseType::CLI, $e->getType()); @@ -40,55 +41,47 @@ public function testMyFirstIoExercise(): void $this->assertFileExists(realpath($e->getProblem())); } - public function testGetArgsCreatesFileWithRandomContentFromFake(): void + public function testWithNoCode(): void { - $e = new MyFirstIo($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); + $this->runExercise('solution-no-code.php'); - $content1 = file_get_contents($path); - unlink($path); + $this->assertVerifyWasNotSuccessful(); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); - - $content2 = file_get_contents($path); - $this->assertNotEquals($content1, $content2); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); } - public function testTearDownRemovesFile(): void + public function testWithIncorrectOutput(): void { - $e = new MyFirstIo($this->filesystem, $this->faker); - $args = $e->getArgs()[0]; - $path = $args[0]; - $this->assertFileExists($path); + $this->runExercise('solution-wrong-output.php'); - $e->tearDown(); + $this->assertVerifyWasNotSuccessful(); - $this->assertFileDoesNotExist($path); + $this->assertOutputWasIncorrect(); } - public function testFunctionRequirements(): void + public function testFailureWhenNotUsingRequiredFunctions(): void { - $e = new MyFirstIo($this->filesystem, $this->faker); - $this->assertEquals(['file_get_contents'], $e->getRequiredFunctions()); - $this->assertEquals(['file'], $e->getBannedFunctions()); + $this->runExercise('wrong-function-requirements.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertOutputWasCorrect(); + + $this->assertResultsHasFailureAndMatches( + FunctionRequirementsFailure::class, + function (FunctionRequirementsFailure $failure) { + self::assertEquals(['file_get_contents'], $failure->getMissingFunctions()); + self::assertEquals([['function' => 'file', 'line' => 3]], $failure->getBannedFunctions()); + + return true; + } + ); } - public function testConfigure(): void + public function testWithCorrectSolution(): void { - $dispatcher = $this->getMockBuilder(ExerciseDispatcher::class) - ->disableOriginalConstructor() - ->getMock(); - - $dispatcher - ->expects($this->once()) - ->method('requireCheck') - ->with(FunctionRequirementsCheck::class); + $this->runExercise('solution-correct.php'); - $e = new MyFirstIo($this->filesystem, $this->faker); - $e->configure($dispatcher); + $this->assertVerifyWasSuccessful(); } } diff --git a/test/Exercise/TimeServerTest.php b/test/Exercise/TimeServerTest.php index ac8e733..cf21995 100644 --- a/test/Exercise/TimeServerTest.php +++ b/test/Exercise/TimeServerTest.php @@ -2,79 +2,51 @@ namespace PhpSchool\LearnYouPhpTest\Exercise; -use Colors\Color; use PhpSchool\LearnYouPhp\Exercise\TimeServer; -use PhpSchool\PhpWorkshop\Check\CheckRepository; -use PhpSchool\PhpWorkshop\Check\PhpLintCheck; -use PhpSchool\PhpWorkshop\Event\EventDispatcher; +use PhpSchool\PhpWorkshop\Application; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; -use PhpSchool\PhpWorkshop\ExerciseRunner\CliRunner; -use PhpSchool\PhpWorkshop\ExerciseRunner\Factory\CliRunnerFactory; -use PhpSchool\PhpWorkshop\ExerciseRunner\RunnerManager; use PhpSchool\PhpWorkshop\Input\Input; -use PhpSchool\PhpWorkshop\Output\StdOutput; use PhpSchool\PhpWorkshop\Result\ComparisonFailure; use PhpSchool\PhpWorkshop\Result\Failure; -use PhpSchool\PhpWorkshop\Result\Success; -use PhpSchool\PhpWorkshop\ResultAggregator; -use PhpSchool\Terminal\Terminal; -use PHPUnit\Framework\TestCase; +use PhpSchool\PhpWorkshop\TestUtils\WorkshopExerciseTest; -class TimeServerTest extends TestCase +class TimeServerTest extends WorkshopExerciseTest { - /** - * @var TimeServer - */ - private $exercise; + public function getApplication(): Application + { + return require __DIR__ . '/../../app/bootstrap.php'; + } - /** - * @var ExerciseDispatcher - */ - private $exerciseDispatcher; + public function getExerciseClass(): string + { + return TimeServer::class; + } - public function setUp(): void + public function testExerciseMeta(): void { - $results = new ResultAggregator(); - $eventDispatcher = new EventDispatcher($results); - - $this->exercise = new TimeServer(); - $runner = new CliRunner($this->exercise, $eventDispatcher); - - $r = new \ReflectionClass($runner); - $rp = $r->getProperty('requiredChecks'); - $rp->setAccessible(true); - $rp->setValue($runner, []); - - $runnerFactory = $this->createPartialMock(CliRunnerFactory::class, ['create']); - $runnerFactory->method('create')->willReturn($runner); - $runnerManager = new RunnerManager(); - $runnerManager->addFactory($runnerFactory); - $this->exerciseDispatcher = new ExerciseDispatcher( - $runnerManager, - $results, - $eventDispatcher, - new CheckRepository([new PhpLintCheck()]) - ); + $e = new TimeServer(); + + $this->assertEquals('Time Server', $e->getName()); + $this->assertEquals('Build a Time Server!', $e->getDescription()); + $this->assertEquals(ExerciseType::CLI, $e->getType()); + + $this->assertFileExists(realpath($e->getProblem())); } - public function testGetters(): void + public function testWithNoCode(): void { - $this->assertEquals('Time Server', $this->exercise->getName()); - $this->assertEquals('Build a Time Server!', $this->exercise->getDescription()); - $this->assertEquals(ExerciseType::CLI, $this->exercise->getType()); + $this->runExercise('solution-no-code.php'); + + $this->assertVerifyWasNotSuccessful(); - $this->assertFileExists(realpath($this->exercise->getProblem())); + $this->assertResultsHasFailure(Failure::class, 'No code was found'); } - public function testFailureIsReturnedIfCannotConnect(): void + public function testFailureWhenCannotConnect(): void { - $input = new Input('learnyouphp', ['program' => __DIR__ . '/../res/time-server/no-server.php']); - $results = $this->exerciseDispatcher->verify($this->exercise, $input); - $this->assertCount(2, $results); + $this->runExercise('solution-no-server.php'); - $failure = iterator_to_array($results)[0]; - $this->assertInstanceOf(Failure::class, $failure); + $this->assertVerifyWasNotSuccessful(); if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $reason = '/^Client returns an error \(number \d+\): No connection could be made because'; @@ -85,44 +57,37 @@ public function testFailureIsReturnedIfCannotConnect(): void $reason .= ' while trying to join tcp:\/\/127\.0\.0\.1:\d+\.$/'; } - $this->assertMatchesRegularExpression($reason, $failure->getReason()); - $this->assertEquals('Time Server', $failure->getCheckName()); - } - - public function testFailureIsReturnedIfOutputWasNotCorrect(): void - { - $input = new Input('learnyouphp', ['program' => __DIR__ . '/../res/time-server/solution-wrong.php']); - $results = $this->exerciseDispatcher->verify($this->exercise, $input); + $this->assertResultsHasFailureAndMatches(Failure::class, function (Failure $failure) use ($reason) { + $this->assertMatchesRegularExpression($reason, $failure->getReason()); - $this->assertCount(2, $results); - $failure = iterator_to_array($results)[0]; - - $this->assertInstanceOf(ComparisonFailure::class, $failure); - $this->assertNotEquals($failure->getExpectedValue(), $failure->getActualValue()); - $this->assertEquals('Time Server', $failure->getCheckName()); + return true; + }); } - public function testSuccessIsReturnedIfOutputIsCorrect(): void + public function testWithIncorrectOutput(): void { - $input = new Input('learnyouphp', ['program' => __DIR__ . '/../res/time-server/solution.php']); - $results = $this->exerciseDispatcher->verify($this->exercise, $input); - - $this->assertCount(2, $results); - $success = iterator_to_array($results)[0]; - $this->assertInstanceOf(Success::class, $success); + $this->runExercise('solution-wrong-output.php'); + + $this->assertVerifyWasNotSuccessful(); + + $this->assertResultsHasFailureAndMatches(ComparisonFailure::class, function (ComparisonFailure $failure) { + static::assertMatchesRegularExpression( + '/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\\n$/', + $failure->getExpectedValue() + ); + static::assertMatchesRegularExpression( + '/^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}\\n$/', + $failure->getActualValue() + ); + + return true; + }); } - public function testRun(): void + public function testWithCorrectSolution(): void { - $color = new Color(); - $color->setForceStyle(true); - $output = new StdOutput($color, $terminal = $this->createMock(Terminal::class)); - - $outputRegEx = '/\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}'; - $outputRegEx .= "\n/"; - $this->expectOutputRegex($outputRegEx); + $this->runExercise('solution-correct.php'); - $input = new Input('learnyouphp', ['program' => __DIR__ . '/../res/time-server/solution.php']); - $this->exerciseDispatcher->run($this->exercise, $input, $output); + $this->assertVerifyWasSuccessful(); } } diff --git a/test/res/concerned-about-separation/include.php b/test/res/concerned-about-separation/include.php deleted file mode 100644 index 3df0360..0000000 --- a/test/res/concerned-about-separation/include.php +++ /dev/null @@ -1,3 +0,0 @@ -getBasename()); +} diff --git a/test/res/time-server/no-server.php b/test/solutions/array-we-go/solution-no-code.php similarity index 100% rename from test/res/time-server/no-server.php rename to test/solutions/array-we-go/solution-no-code.php diff --git a/test/solutions/array-we-go/solution-wrong-output.php b/test/solutions/array-we-go/solution-wrong-output.php new file mode 100644 index 0000000..1978d73 --- /dev/null +++ b/test/solutions/array-we-go/solution-wrong-output.php @@ -0,0 +1,12 @@ +getBasename()); +} diff --git a/test/solutions/baby-steps/solution-correct.php b/test/solutions/baby-steps/solution-correct.php new file mode 100644 index 0000000..a95d415 --- /dev/null +++ b/test/solutions/baby-steps/solution-correct.php @@ -0,0 +1,8 @@ +getFiles($argv[1], $argv[2])); \ No newline at end of file diff --git a/test/solutions/concerned-about-separation/no-include.php b/test/solutions/concerned-about-separation/no-include.php new file mode 100644 index 0000000..9dd8a50 --- /dev/null +++ b/test/solutions/concerned-about-separation/no-include.php @@ -0,0 +1,23 @@ +getFiles($argv[1], $argv[2])); \ No newline at end of file diff --git a/test/solutions/concerned-about-separation/solution-no-code.php b/test/solutions/concerned-about-separation/solution-no-code.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/test/solutions/concerned-about-separation/solution-no-code.php @@ -0,0 +1 @@ +query('SELECT * FROM users WHERE age > 30'); +foreach ($users as $user) { + echo "User: {$user['name']} Age: {$user['age']} Sex: {$user['gender']}\n"; +} +$nameToUpdate = $argv[2]; +$stmt = $db->prepare('UPDATE users SET name = :newName WHERE name = :oldName'); +$stmt->execute([':newName' => 'David Attenborough', ':oldName' => $nameToUpdate]); \ No newline at end of file diff --git a/test/solutions/database-read/solution-no-code.php b/test/solutions/database-read/solution-no-code.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/test/solutions/database-read/solution-no-code.php @@ -0,0 +1 @@ +query('SELECT * FROM users WHERE age > 30'); +foreach ($users as $user) { + echo "User: {$user['name']}\n"; +} +$nameToUpdate = $argv[2]; +$stmt = $db->prepare('UPDATE users SET name = :newName WHERE name = :oldName'); +$stmt->execute([':newName' => 'David Attenborough', ':oldName' => $nameToUpdate]); \ No newline at end of file diff --git a/test/solutions/database-read/solution-wrong-update.php b/test/solutions/database-read/solution-wrong-update.php new file mode 100644 index 0000000..f66de34 --- /dev/null +++ b/test/solutions/database-read/solution-wrong-update.php @@ -0,0 +1,10 @@ +query('SELECT * FROM users WHERE age > 30'); +foreach ($users as $user) { + echo "User: {$user['name']} Age: {$user['age']} Sex: {$user['gender']}\n"; +} +$nameToUpdate = $argv[2]; +$stmt = $db->prepare('UPDATE users SET name = :newName WHERE name = :oldName'); +$stmt->execute([':newName' => 'Wrong name', ':oldName' => $nameToUpdate]); \ No newline at end of file diff --git a/test/solutions/dependency-heaven/no-composer/solution.php b/test/solutions/dependency-heaven/no-composer/solution.php index a814366..5c881ba 100644 --- a/test/solutions/dependency-heaven/no-composer/solution.php +++ b/test/solutions/dependency-heaven/no-composer/solution.php @@ -1 +1,3 @@ -getBasename()); + } catch (RuntimeException $e) { + echo sprintf("Unable to open file at path '%s'\n", $filePath); + } +} diff --git a/test/solutions/exceptional-coding/solution-no-code.php b/test/solutions/exceptional-coding/solution-no-code.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/test/solutions/exceptional-coding/solution-no-code.php @@ -0,0 +1 @@ +getExtension() === $argv[2]) { + echo $file->getFilename() . "\n"; + } +} diff --git a/test/solutions/filtered-ls/solution-no-code.php b/test/solutions/filtered-ls/solution-no-code.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/test/solutions/filtered-ls/solution-no-code.php @@ -0,0 +1 @@ +getFilename() . "\n"; +} diff --git a/test/solutions/hello-world/solution-correct.php b/test/solutions/hello-world/solution-correct.php new file mode 100644 index 0000000..76b91b1 --- /dev/null +++ b/test/solutions/hello-world/solution-correct.php @@ -0,0 +1,3 @@ + $date->format('H'), + "minute" => $date->format('i'), + "second" => $date->format('s') + ]); + exit; + } +} + +if ($urlParts['path'] === '/api/unixtime') { + if (isset($_GET['iso'])) { + $date = new \DateTime($_GET['iso']); + echo json_encode(["unixtime" => $date->format('U')]); + exit; + } +} diff --git a/test/solutions/http-json-api/solution-no-code.php b/test/solutions/http-json-api/solution-no-code.php new file mode 100644 index 0000000..b3d9bbc --- /dev/null +++ b/test/solutions/http-json-api/solution-no-code.php @@ -0,0 +1 @@ + $date->format('H'), + "minutes" => $date->format('i'), + "seconds" => $date->format('s') + ]); + exit; + } +} + +if ($urlParts['path'] === '/api/unixtime') { + if (isset($_GET['iso'])) { + $date = new \DateTime($_GET['iso']); + echo json_encode(["unixtime" => $date->format('U')]); + exit; + } +} diff --git a/test/solutions/my-first-io/solution-correct.php b/test/solutions/my-first-io/solution-correct.php new file mode 100644 index 0000000..ef663a5 --- /dev/null +++ b/test/solutions/my-first-io/solution-correct.php @@ -0,0 +1,4 @@ + Date: Sat, 25 May 2024 23:11:35 +0200 Subject: [PATCH 2/5] Update workshop --- composer.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/composer.lock b/composer.lock index e6c5380..2ee4050 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "205aa5b5714933bfd2300ccbfdadbd28", + "content-hash": "6c6ac0e6333aeeee959f1080ff6b2a90", "packages": [ { "name": "beberlei/assert", @@ -1130,12 +1130,12 @@ "source": { "type": "git", "url": "https://github.com/php-school/php-workshop.git", - "reference": "065947216d954289c70826afe27ccf0b6f49f109" + "reference": "353e5e35602719dcf133996a06bf6ee1c322d097" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-school/php-workshop/zipball/065947216d954289c70826afe27ccf0b6f49f109", - "reference": "065947216d954289c70826afe27ccf0b6f49f109", + "url": "https://api.github.com/repos/php-school/php-workshop/zipball/353e5e35602719dcf133996a06bf6ee1c322d097", + "reference": "353e5e35602719dcf133996a06bf6ee1c322d097", "shasum": "" }, "require": { @@ -1211,7 +1211,7 @@ "issues": "https://github.com/php-school/php-workshop/issues", "source": "https://github.com/php-school/php-workshop/tree/docker-fixes" }, - "time": "2024-05-25T13:59:57+00:00" + "time": "2024-05-25T21:10:47+00:00" }, { "name": "php-school/terminal", @@ -3755,6 +3755,7 @@ "prefer-lowest": false, "platform": { "php": ">=8.0", + "ext-pdo": "*", "ext-pdo_sqlite": "*", "ext-sockets": "*" }, From 86b1917e9a193060d53679256e329c51144a8125 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 26 May 2024 22:12:34 +0200 Subject: [PATCH 3/5] Fix phpunit config --- phpunit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit.xml b/phpunit.xml index 760aa23..aeee4bb 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,6 +11,6 @@ ./test/solutions - + From 2906141bc9dee9cb702b40166487f06f692f45a9 Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Sun, 26 May 2024 22:20:21 +0200 Subject: [PATCH 4/5] Update workshop --- composer.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.lock b/composer.lock index 2ee4050..9f2c85b 100644 --- a/composer.lock +++ b/composer.lock @@ -1130,12 +1130,12 @@ "source": { "type": "git", "url": "https://github.com/php-school/php-workshop.git", - "reference": "353e5e35602719dcf133996a06bf6ee1c322d097" + "reference": "6f461ac93816ddedf17d7a2b3af20f9afcd42b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-school/php-workshop/zipball/353e5e35602719dcf133996a06bf6ee1c322d097", - "reference": "353e5e35602719dcf133996a06bf6ee1c322d097", + "url": "https://api.github.com/repos/php-school/php-workshop/zipball/6f461ac93816ddedf17d7a2b3af20f9afcd42b23", + "reference": "6f461ac93816ddedf17d7a2b3af20f9afcd42b23", "shasum": "" }, "require": { @@ -1211,7 +1211,7 @@ "issues": "https://github.com/php-school/php-workshop/issues", "source": "https://github.com/php-school/php-workshop/tree/docker-fixes" }, - "time": "2024-05-25T21:10:47+00:00" + "time": "2024-05-26T20:19:31+00:00" }, { "name": "php-school/terminal", From 9269bc6ebdf3b28e5b52f2ad81e28636de07481e Mon Sep 17 00:00:00 2001 From: Aydin Hassan Date: Wed, 4 Sep 2024 22:14:29 +0200 Subject: [PATCH 5/5] Try fix timeserver --- composer.lock | 8 +- src/Exercise/TimeServer.php | 139 ++++++++++++++++++++----------- test/Exercise/TimeServerTest.php | 10 +-- 3 files changed, 96 insertions(+), 61 deletions(-) diff --git a/composer.lock b/composer.lock index 9f2c85b..0196760 100644 --- a/composer.lock +++ b/composer.lock @@ -1130,12 +1130,12 @@ "source": { "type": "git", "url": "https://github.com/php-school/php-workshop.git", - "reference": "6f461ac93816ddedf17d7a2b3af20f9afcd42b23" + "reference": "73febf8c62d982cd74896df9266522dc2a5153b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-school/php-workshop/zipball/6f461ac93816ddedf17d7a2b3af20f9afcd42b23", - "reference": "6f461ac93816ddedf17d7a2b3af20f9afcd42b23", + "url": "https://api.github.com/repos/php-school/php-workshop/zipball/73febf8c62d982cd74896df9266522dc2a5153b5", + "reference": "73febf8c62d982cd74896df9266522dc2a5153b5", "shasum": "" }, "require": { @@ -1211,7 +1211,7 @@ "issues": "https://github.com/php-school/php-workshop/issues", "source": "https://github.com/php-school/php-workshop/tree/docker-fixes" }, - "time": "2024-05-26T20:19:31+00:00" + "time": "2024-09-04T11:22:50+00:00" }, { "name": "php-school/terminal", diff --git a/src/Exercise/TimeServer.php b/src/Exercise/TimeServer.php index ae1dada..ad5ee12 100644 --- a/src/Exercise/TimeServer.php +++ b/src/Exercise/TimeServer.php @@ -10,7 +10,6 @@ use PhpSchool\PhpWorkshop\Exercise\ExerciseInterface; use PhpSchool\PhpWorkshop\Exercise\ExerciseType; use PhpSchool\PhpWorkshop\Exercise\Scenario\CliScenario; -use PhpSchool\PhpWorkshop\ExerciseDispatcher; use PhpSchool\PhpWorkshop\Output\OutputInterface; use PhpSchool\PhpWorkshop\Result\ComparisonFailure; use PhpSchool\PhpWorkshop\Result\Failure; @@ -31,71 +30,101 @@ public function getDescription(): string public function defineListeners(EventDispatcher $eventDispatcher): void { - $appendArgsListener = function (CliExecuteEvent $event) { - $event->appendArg('127.0.0.1'); - $event->appendArg($this->getRandomPort()); - }; + $referencePort = $this->getRandomPort(); + $studentPort = $this->getRandomPort(); + + $eventDispatcher->listen( + 'cli.verify.reference-execute.pre', + function (CliExecuteEvent $event) use ($referencePort) { + $event->appendArg('0.0.0.0'); + $event->appendArg((string) $referencePort); + $event->getScenario()->exposePort($referencePort); + } + ); + $eventDispatcher->listen( + ['cli.verify.student-execute.pre', 'cli.run.student-execute.pre'], + function (CliExecuteEvent $event) use ($studentPort) { + $event->appendArg('0.0.0.0'); + $event->appendArg((string) $studentPort); + $event->getScenario()->exposePort($studentPort); + } + ); - $eventDispatcher->listen('cli.verify.reference-execute.pre', $appendArgsListener); - $eventDispatcher->listen('cli.verify.student-execute.pre', $appendArgsListener); - $eventDispatcher->listen('cli.run.student-execute.pre', $appendArgsListener); + $eventDispatcher->listen( + 'cli.verify.reference.executing', + function (CliExecuteEvent $event) use ($referencePort) { + //wait for server to boot + sleep(1); - $eventDispatcher->listen('cli.verify.reference.executing', function (CliExecuteEvent $event) { - $args = $event->getArgs()->getArrayCopy(); + $socket = $this->createSocket(); + @socket_connect($socket, '0.0.0.0', $referencePort); + @socket_read($socket, 2048, PHP_NORMAL_READ); - //wait for server to boot - usleep(100000); + socket_close($socket); - $socket = $this->createSocket(); - socket_connect($socket, $args[0], (int) $args[1]); - socket_read($socket, 2048, PHP_NORMAL_READ); + //wait for shutdown + usleep(100000); + } + ); - //wait for shutdown - usleep(100000); - }); + $eventDispatcher->insertVerifier( + 'cli.verify.student.executing', + function (CliExecuteEvent $event) use ($studentPort) { + //wait for server to boot + sleep(1); - $eventDispatcher->insertVerifier('cli.verify.student.executing', function (CliExecuteEvent $event) { - $args = $event->getArgs()->getArrayCopy(); + $socket = $this->createSocket(); - //wait for server to boot - usleep(100000); + $result = @socket_connect($socket, '0.0.0.0', $studentPort); - $socket = $this->createSocket(); - $connectResult = @socket_connect($socket, $args[0], (int) $args[1]); - - if (!$connectResult) { - return Failure::fromNameAndReason($this->getName(), sprintf( - "Client returns an error (number %d): Connection refused while trying to join tcp://127.0.0.1:%d.", - socket_last_error($socket), - $args[1] - )); - } + if (!$result) { + $error = "Client returns an error (number %d): Connection refused "; + $error .= "while trying to join tcp://0.0.0.0:%d."; - $out = (string) socket_read($socket, 2048, PHP_NORMAL_READ); + return Failure::fromNameAndReason($this->getName(), sprintf( + $error, + socket_last_error($socket), + $studentPort + )); + } - //wait for shutdown - usleep(100000); + $out = (string) socket_read($socket, 2048, PHP_NORMAL_READ); + + socket_close($socket); - $date = new \DateTime(); + //wait for shutdown + usleep(100000); - //match the current date but any seconds - //since we can't mock time in PHP easily - if (!preg_match(sprintf('/^%s:([0-5][0-9]|60)\n$/', $date->format('Y-m-d H:i')), $out)) { - return ComparisonFailure::fromNameAndValues($this->getName(), $date->format("Y-m-d H:i:s\n"), $out); + $date = new \DateTime(); + + //match the current date but any seconds + //since we can't mock time in PHP easily + if (!preg_match(sprintf('/^%s:([0-5][0-9]|60)\n$/', $date->format('Y-m-d H:i')), $out)) { + return ComparisonFailure::fromNameAndValues($this->getName(), $date->format("Y-m-d H:i:s\n"), $out); + } + return new Success($this->getName()); } - return new Success($this->getName()); - }); + ); - $eventDispatcher->listen('cli.run.student.executing', function (CliExecuteEvent $event) { + $eventDispatcher->listen('cli.run.student.executing', function (CliExecuteEvent $event) use ($studentPort) { /** @var OutputInterface $output */ $output = $event->getParameter('output'); - $args = $event->getArgs()->getArrayCopy(); //wait for server to boot - usleep(100000); + sleep(1); $socket = $this->createSocket(); - socket_connect($socket, $args[0], (int) $args[1]); + try { + $connectResult = @socket_connect($socket, '0.0.0.0', $studentPort); + } catch (\ErrorException $e) { + $output->write('Cannot connect'); + return; + } + + if (false === $connectResult) { + $output->write('Cannot connect'); + return; + } $out = (string) socket_read($socket, 2048, PHP_NORMAL_READ); //wait for shutdown @@ -105,9 +134,18 @@ public function defineListeners(EventDispatcher $eventDispatcher): void }); } - private function getRandomPort(): string + private function getRandomPort(): int { - return (string) mt_rand(1025, 65535); + $sock = socket_create_listen(0); + + if ($sock === false) { + throw new RuntimeException('Cannot create socket'); + } + + socket_getsockname($sock, $addr, $port); + socket_close($sock); + + return $port; } public function getType(): ExerciseType @@ -117,7 +155,8 @@ public function getType(): ExerciseType public function defineTestScenario(): CliScenario { - return (new CliScenario())->withExecution(); + return (new CliScenario()) + ->withExecution(); } private function createSocket(): Socket @@ -128,6 +167,8 @@ private function createSocket(): Socket throw new RuntimeException('Cannot create socket'); } + socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ["sec" => 5, "usec" => 0]); + return $socket; } } diff --git a/test/Exercise/TimeServerTest.php b/test/Exercise/TimeServerTest.php index cf21995..ea976d6 100644 --- a/test/Exercise/TimeServerTest.php +++ b/test/Exercise/TimeServerTest.php @@ -48,14 +48,8 @@ public function testFailureWhenCannotConnect(): void $this->assertVerifyWasNotSuccessful(); - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - $reason = '/^Client returns an error \(number \d+\): No connection could be made because'; - $reason .= ' the target machine actively refused it\.\r\n'; - $reason .= ' while trying to join tcp:\/\/127\.0\.0\.1:\d+\.$/'; - } else { - $reason = '/^Client returns an error \(number \d+\): Connection refused'; - $reason .= ' while trying to join tcp:\/\/127\.0\.0\.1:\d+\.$/'; - } + $reason = '/^Client returns an error \(number \d+\): Connection refused'; + $reason .= ' while trying to join tcp:\/\/0\.0\.0\.0:\d+\.$/'; $this->assertResultsHasFailureAndMatches(Failure::class, function (Failure $failure) use ($reason) { $this->assertMatchesRegularExpression($reason, $failure->getReason());