From 5eb0cc261ea11ce8ff4d8027816d214d5451cc64 Mon Sep 17 00:00:00 2001 From: Lucas Bustamante Date: Fri, 23 Feb 2024 21:01:59 -0300 Subject: [PATCH] Env up, list and down --- src/composer.json | 1 + src/composer.lock | 322 +++++++++++------- src/src/Cache.php | 1 + src/src/Commands/CreateMassTestCommands.php | 2 +- src/src/Commands/CreateRunCommands.php | 2 +- src/src/Commands/DynamicCommandCreator.php | 10 +- .../Environment/DownEnvironmentCommand.php | 74 ++++ .../Environment/ListEnvironmentCommand.php | 66 ++++ .../Environment/RestartEnvironmentCommand.php | 53 +++ .../Environment/UpEnvironmentCommand.php | 61 ++++ src/src/Environment/Docker.php | 34 ++ src/src/Environment/E2EEnvironment.php | 133 ++++++++ src/src/Environment/EnvInfo.php | 34 ++ src/src/Environment/Environment.php | 13 + src/src/Environment/EnvironmentMonitor.php | 68 ++++ src/src/bootstrap.php | 22 ++ src/src/helpers.php | 21 ++ src/tests/EnvironmentMonitorTest.php | 86 +++++ src/tests/RestartEnvironmentTest.php | 74 ++++ ...MonitorTest__testEnvironmentUpdate__1.json | 8 + ...tMonitorTest__test_environment_add__1.json | 8 + ...st__test_environment_adds_multiple__1.json | 14 + ...itorTest__test_environment_stopped__1.json | 1 + ...st_environment_stops_with_multiple__1.json | 8 + ...itorTest__test_environment_updated__1.json | 8 + ...est__test_makes_env_info_from_path__1.json | 6 + ...testRestartEnvironmentMultipleTimes__1.php | 4 + ...startEnvironmentWithMultipleOptions__1.php | 4 + ...__testRestartEnvironmentWithOptions__1.php | 4 + ...ronmentTest__testRestartEnvironment__1.php | 4 + 30 files changed, 1014 insertions(+), 132 deletions(-) create mode 100644 src/src/Commands/Environment/DownEnvironmentCommand.php create mode 100644 src/src/Commands/Environment/ListEnvironmentCommand.php create mode 100644 src/src/Commands/Environment/RestartEnvironmentCommand.php create mode 100644 src/src/Commands/Environment/UpEnvironmentCommand.php create mode 100644 src/src/Environment/Docker.php create mode 100644 src/src/Environment/E2EEnvironment.php create mode 100644 src/src/Environment/EnvInfo.php create mode 100644 src/src/Environment/Environment.php create mode 100644 src/src/Environment/EnvironmentMonitor.php create mode 100644 src/tests/EnvironmentMonitorTest.php create mode 100644 src/tests/RestartEnvironmentTest.php create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__testEnvironmentUpdate__1.json create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_add__1.json create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_adds_multiple__1.json create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stopped__1.json create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stops_with_multiple__1.json create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_updated__1.json create mode 100644 src/tests/__snapshots__/EnvironmentMonitorTest__test_makes_env_info_from_path__1.json create mode 100644 src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironmentMultipleTimes__1.php create mode 100644 src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironmentWithMultipleOptions__1.php create mode 100644 src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironmentWithOptions__1.php create mode 100644 src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironment__1.php diff --git a/src/composer.json b/src/composer.json index 7c8d3f1c..22959da4 100644 --- a/src/composer.json +++ b/src/composer.json @@ -23,6 +23,7 @@ "ext-curl": "*", "ext-zip": "*", "symfony/console": "^5", + "symfony/filesystem": "^5", "lucatume/di52": "^3", "stecman/symfony-console-completion": "^0.11.0" }, diff --git a/src/composer.lock b/src/composer.lock index c5b30a2c..45cb7504 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e947aa26f87afcf701a684d54618890a", + "content-hash": "4adf4b66727aa6fc6d98408f4bc261ad", "packages": [ { "name": "lucatume/di52", - "version": "3.3.2", + "version": "3.3.5", "source": { "type": "git", "url": "https://github.com/lucatume/di52.git", - "reference": "a063c9a18819b00a25883aca65b1d09697d8abf0" + "reference": "d39d1cbbc57eb41c7aa21fab106e17b6938ec6b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lucatume/di52/zipball/a063c9a18819b00a25883aca65b1d09697d8abf0", - "reference": "a063c9a18819b00a25883aca65b1d09697d8abf0", + "url": "https://api.github.com/repos/lucatume/di52/zipball/d39d1cbbc57eb41c7aa21fab106e17b6938ec6b3", + "reference": "d39d1cbbc57eb41c7aa21fab106e17b6938ec6b3", "shasum": "" }, "require": { @@ -47,9 +47,9 @@ "description": "A PHP 5.6 compatible dependency injection container.", "support": { "issues": "https://github.com/lucatume/di52/issues", - "source": "https://github.com/lucatume/di52/tree/3.3.2" + "source": "https://github.com/lucatume/di52/tree/3.3.5" }, - "time": "2023-04-07T12:38:10+00:00" + "time": "2023-09-01T08:49:32+00:00" }, { "name": "psr/container", @@ -150,16 +150,16 @@ }, { "name": "symfony/console", - "version": "v5.4.22", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8" + "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3cd51fd2e6c461ca678f84d419461281bd87a0a8", - "reference": "3cd51fd2e6c461ca678f84d419461281bd87a0a8", + "url": "https://api.github.com/repos/symfony/console/zipball/dbdf6adcb88d5f83790e1efb57ef4074309d3931", + "reference": "dbdf6adcb88d5f83790e1efb57ef4074309d3931", "shasum": "" }, "require": { @@ -229,7 +229,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.22" + "source": "https://github.com/symfony/console/tree/v5.4.35" }, "funding": [ { @@ -245,7 +245,7 @@ "type": "tidelift" } ], - "time": "2023-03-25T09:27:28+00:00" + "time": "2024-01-23T14:28:09+00:00" }, { "name": "symfony/deprecation-contracts", @@ -314,18 +314,82 @@ ], "time": "2022-01-02T09:53:40+00:00" }, + { + "name": "symfony/filesystem", + "version": "v5.4.35", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/5a553607d4ffbfa9c0ab62facadea296c9db7086", + "reference": "5a553607d4ffbfa9c0ab62facadea296c9db7086", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v5.4.35" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-23T13:51:25+00:00" + }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -339,9 +403,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -378,7 +439,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -394,20 +455,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", - "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -418,9 +479,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -459,7 +517,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -475,20 +533,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", - "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -499,9 +557,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -543,7 +598,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -559,20 +614,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -586,9 +641,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -626,7 +678,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -642,20 +694,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", "shasum": "" }, "require": { @@ -663,9 +715,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -705,7 +754,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" }, "funding": [ { @@ -721,20 +770,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -742,9 +791,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -788,7 +834,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -804,7 +850,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/service-contracts", @@ -891,16 +937,16 @@ }, { "name": "symfony/string", - "version": "v5.4.22", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62" + "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", - "reference": "8036a4c76c0dd29e60b6a7cafcacc50cf088ea62", + "url": "https://api.github.com/repos/symfony/string/zipball/c209c4d0559acce1c9a2067612cfb5d35756edc2", + "reference": "c209c4d0559acce1c9a2067612cfb5d35756edc2", "shasum": "" }, "require": { @@ -957,7 +1003,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.22" + "source": "https://github.com/symfony/string/tree/v5.4.35" }, "funding": [ { @@ -973,7 +1019,7 @@ "type": "tidelift" } ], - "time": "2023-03-14T06:11:53+00:00" + "time": "2024-01-23T13:51:25+00:00" } ], "packages-dev": [ @@ -1356,16 +1402,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.13", + "version": "1.10.59", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70" + "reference": "e607609388d3a6d418a50a49f7940e8086798281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f07bf8c6980b81bf9e49d44bd0caf2e737614a70", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e607609388d3a6d418a50a49f7940e8086798281", + "reference": "e607609388d3a6d418a50a49f7940e8086798281", "shasum": "" }, "require": { @@ -1414,7 +1460,7 @@ "type": "tidelift" } ], - "time": "2023-04-12T19:29:52+00:00" + "time": "2024-02-20T13:59:13+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1715,16 +1761,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.5.33", + "version": "8.5.36", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e" + "reference": "9652df58e06a681429d8cfdaec3c43d6de581d5a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e", - "reference": "7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9652df58e06a681429d8cfdaec3c43d6de581d5a", + "reference": "9652df58e06a681429d8cfdaec3c43d6de581d5a", "shasum": "" }, "require": { @@ -1754,9 +1800,9 @@ "sebastian/version": "^2.0.1" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0.0" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage", + "phpunit/php-invoker": "To allow enforcing time limits" }, "bin": [ "phpunit" @@ -1792,7 +1838,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.33" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.36" }, "funding": [ { @@ -1808,7 +1855,7 @@ "type": "tidelift" } ], - "time": "2023-02-27T13:04:50+00:00" + "time": "2023-12-01T16:52:15+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1941,16 +1988,16 @@ }, { "name": "sebastian/diff", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae", + "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae", "shasum": "" }, "require": { @@ -1995,7 +2042,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4" }, "funding": [ { @@ -2003,7 +2050,7 @@ "type": "github" } ], - "time": "2020-11-30T07:59:04+00:00" + "time": "2023-05-07T05:30:20+00:00" }, { "name": "sebastian/environment", @@ -2147,16 +2194,16 @@ }, { "name": "sebastian/global-state", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" + "reference": "66783ce213de415b451b904bfef9dda0cf9aeae0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/66783ce213de415b451b904bfef9dda0cf9aeae0", + "reference": "66783ce213de415b451b904bfef9dda0cf9aeae0", "shasum": "" }, "require": { @@ -2199,7 +2246,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.3" }, "funding": [ { @@ -2207,7 +2254,7 @@ "type": "github" } ], - "time": "2022-02-10T06:55:38+00:00" + "time": "2023-08-02T09:23:32+00:00" }, { "name": "sebastian/object-enumerator", @@ -2595,16 +2642,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.9.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", "shasum": "" }, "require": { @@ -2614,11 +2661,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -2633,35 +2680,58 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-02-16T15:06:51+00:00" }, { "name": "symfony/yaml", - "version": "v5.4.21", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3713e20d93e46e681e51605d213027e48dab3469" + "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3713e20d93e46e681e51605d213027e48dab3469", - "reference": "3713e20d93e46e681e51605d213027e48dab3469", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e78db7f5c70a21f0417a31f414c4a95fe76c07e4", + "reference": "e78db7f5c70a21f0417a31f414c4a95fe76c07e4", "shasum": "" }, "require": { @@ -2707,7 +2777,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v5.4.21" + "source": "https://github.com/symfony/yaml/tree/v5.4.35" }, "funding": [ { @@ -2723,20 +2793,20 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", "shasum": "" }, "require": { @@ -2765,7 +2835,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.2" }, "funding": [ { @@ -2773,7 +2843,7 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" + "time": "2023-11-20T00:12:19+00:00" }, { "name": "wp-coding-standards/wpcs", @@ -2841,5 +2911,5 @@ "platform-overrides": { "php": "7.2.5" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/src/Cache.php b/src/src/Cache.php index cf02a42a..9a404a12 100644 --- a/src/src/Cache.php +++ b/src/src/Cache.php @@ -2,6 +2,7 @@ namespace QIT_CLI; +use QIT_CLI\Environment\EnvInfo; use QIT_CLI\IO\Output; class Cache { diff --git a/src/src/Commands/CreateMassTestCommands.php b/src/src/Commands/CreateMassTestCommands.php index 17700154..99c06901 100644 --- a/src/src/Commands/CreateMassTestCommands.php +++ b/src/src/Commands/CreateMassTestCommands.php @@ -69,7 +69,7 @@ public function execute( InputInterface $input, OutputInterface $output ) { return; } - $this->add_schema_to_command( $command, $schema ); + static::add_schema_to_command( $command, $schema ); $application->add( $command ); } diff --git a/src/src/Commands/CreateRunCommands.php b/src/src/Commands/CreateRunCommands.php index d57896de..8afc9824 100644 --- a/src/src/Commands/CreateRunCommands.php +++ b/src/src/Commands/CreateRunCommands.php @@ -251,7 +251,7 @@ public function execute( InputInterface $input, OutputInterface $output ) { $command ->setName( "run:$test_type" ); - $this->add_schema_to_command( $command, $schema ); + static::add_schema_to_command( $command, $schema ); // Extension slug/ID. $command->addArgument( diff --git a/src/src/Commands/DynamicCommandCreator.php b/src/src/Commands/DynamicCommandCreator.php index b693f33b..68d2912a 100644 --- a/src/src/Commands/DynamicCommandCreator.php +++ b/src/src/Commands/DynamicCommandCreator.php @@ -2,9 +2,11 @@ namespace QIT_CLI\Commands; +use QIT_CLI\App; +use QIT_CLI\IO\Output; use Symfony\Component\Console\Application; +use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Output\OutputInterface; abstract class DynamicCommandCreator { abstract public function register_commands( Application $application ): void; @@ -15,7 +17,7 @@ abstract public function register_commands( Application $application ): void; * * @return void */ - protected function add_schema_to_command( DynamicCommand $command, array $schema ): void { + public static function add_schema_to_command( Command $command, array $schema, array $exceptions = [] ): void { if ( ! empty( $schema['description'] ) ) { $command->setDescription( $schema['description'] ); } @@ -24,7 +26,7 @@ protected function add_schema_to_command( DynamicCommand $command, array $schema foreach ( $schema['properties'] as $property_name => $property_schema ) { $ignore = [ 'client', 'event', 'woo_id', 'is_product_update', 'upload_id' ]; - if ( in_array( $property_name, $ignore, true ) ) { + if ( in_array( $property_name, array_merge( $exceptions, $ignore ), true ) ) { continue; } @@ -74,7 +76,7 @@ protected function add_schema_to_command( DynamicCommand $command, array $schema } } - if ( isset( $this->output ) && $this->output instanceof OutputInterface && $this->output->isVerbose() ) { + if ( App::make( Output::class )->isVerbose() ) { $schema_to_show = $property_schema; unset( $schema_to_show['description'] ); $description = sprintf( '%s (Schema: %s)', $description, json_encode( $schema_to_show ) ); diff --git a/src/src/Commands/Environment/DownEnvironmentCommand.php b/src/src/Commands/Environment/DownEnvironmentCommand.php new file mode 100644 index 00000000..107a9083 --- /dev/null +++ b/src/src/Commands/Environment/DownEnvironmentCommand.php @@ -0,0 +1,74 @@ +e2e_environment = $e2e_environment; + $this->environment_monitor = $environment_monitor; + parent::__construct( static::$defaultName ); + } + + protected function configure() { + $this->setDescription( 'Stops a local test environment.' ); + } + + protected function execute( InputInterface $input, OutputInterface $output ): int { + $running_environments = $this->environment_monitor->get(); + + if ( empty( $running_environments ) ) { + $output->writeln( "No environments running." ); + + return Command::SUCCESS; + } + + if ( count( $running_environments ) === 1 ) { + // Stop the single running environment + $this->stopEnvironment( array_key_first( $running_environments ), $output ); + + return Command::SUCCESS; + } + + $running_environments = array_map( function ( EnvInfo $environment ) { + return sprintf( '%s (started %s ago)', $environment->temporary_env, format_elapsed_time( time() - $environment->started ) ); + }, $running_environments ); + + // More than one environment running, let user choose which one to stop + $helper = new QuestionHelper(); + $question = new ChoiceQuestion( + 'Please select the environment to stop (defaults to the first):', + array_keys( $running_environments ), + 0 + ); + $question->setErrorMessage( 'Environment %s is invalid.' ); + + $selectedEnvironment = $helper->ask( $input, $output, $question ); + $this->stopEnvironment( $selectedEnvironment, $output ); + + return Command::SUCCESS; + } + + private function stopEnvironment( string $temporary_environment, OutputInterface $output ) { + // Implement the logic to stop the environment + $this->e2e_environment->down( $temporary_environment ); + $output->writeln( "Environment '$temporary_environment' stopped." ); + } +} diff --git a/src/src/Commands/Environment/ListEnvironmentCommand.php b/src/src/Commands/Environment/ListEnvironmentCommand.php new file mode 100644 index 00000000..fa295a62 --- /dev/null +++ b/src/src/Commands/Environment/ListEnvironmentCommand.php @@ -0,0 +1,66 @@ +environment_monitor = $environment_monitor; + parent::__construct( static::$defaultName ); + } + + protected function configure() { + $this->setDescription( 'List running environments.' ); + } + + protected function execute( InputInterface $input, OutputInterface $output ): int { + $running = $this->environment_monitor->get(); + + if ( empty( $running ) ) { + $output->writeln( "No environments running." ); + + return Command::SUCCESS; + } + + $io = new SymfonyStyle( $input, $output ); + + $output->writeln( "Running environments:" ); + + $definitions = []; + + foreach ( $running as $environment ) { + /** @var EnvInfo $environment */ + $elapsed = format_elapsed_time( time() - $environment->created_at ); + + foreach ( $environment as $k => $v ) { + if ( $k === 'created_at' ) { + $v = $elapsed; + } + $definitions[] = [ ucwords( $k ) => $v ]; + } + + // Add a separator between environments + $definitions[] = new TableSeparator(); + } + + // Remove the last separator + array_pop( $definitions ); + + $io->definitionList( ...$definitions ); + + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/src/src/Commands/Environment/RestartEnvironmentCommand.php b/src/src/Commands/Environment/RestartEnvironmentCommand.php new file mode 100644 index 00000000..d044c523 --- /dev/null +++ b/src/src/Commands/Environment/RestartEnvironmentCommand.php @@ -0,0 +1,53 @@ +e2e_environment = $e2e_environment; + $this->cache = $cache; + $this->up_command = $up_command; + $this->down_command = $down_command; + parent::__construct( static::$defaultName ); + } + + protected function configure() { + $this->setDescription( 'Restarts the local test environment.' ); + } + + protected function execute( InputInterface $input, OutputInterface $output ): int { + $environment_options = $this->cache->get( 'environment_up_options' ) ?? []; + + $this->down_command->run( new ArrayInput( [] ), $output ); + + // Prepend $environment_options with "--" when it's valid UpEnvironmentCommand option. + foreach ( $environment_options as $key => $value ) { + if ( $this->up_command->getDefinition()->hasOption( $key ) ) { + $environment_options[ "--$key" ] = $value; + unset( $environment_options[ $key ] ); + } + } + + return $this->up_command->run( new ArrayInput( $environment_options ), $output ); + } +} \ No newline at end of file diff --git a/src/src/Commands/Environment/UpEnvironmentCommand.php b/src/src/Commands/Environment/UpEnvironmentCommand.php new file mode 100644 index 00000000..8f96052f --- /dev/null +++ b/src/src/Commands/Environment/UpEnvironmentCommand.php @@ -0,0 +1,61 @@ +e2e_environment = $e2e_environment; + $this->cache = $cache; + parent::__construct( static::$defaultName ); + } + + protected function configure() { + $schemas = $this->cache->get_manager_sync_data( 'schemas' ); + + if ( ! is_array( $schemas['e2e']['properties'] ) ) { + throw new \RuntimeException( 'E2E schema not set or incomplete.' ); + } + + DynamicCommandCreator::add_schema_to_command( $this, $schemas['e2e'], [ 'compatibility' ] ); + + $this->setDescription( 'Starts the local test environment.' ) + ->setAliases( [ 'env:start' ] ); + } + + protected function execute( InputInterface $input, OutputInterface $output ): int { + try { + $options = $this->parse_options( $input ); + } catch ( \Exception $e ) { + $output->writeln( sprintf( '%s', $e->getMessage() ) ); + + return Command::FAILURE; + } + + if ( $output->isVeryVerbose() ) { + // Print the current options being used. + $output->writeln( sprintf( 'Starting environment with options: %s', json_encode( $options ) ) ); + } + + $this->cache->set( 'environment_up_options', $options, DAY_IN_SECONDS ); + + $this->e2e_environment->up(); + + $output->writeln( "Environment up." ); + + return Command::SUCCESS; + } +} \ No newline at end of file diff --git a/src/src/Environment/Docker.php b/src/src/Environment/Docker.php new file mode 100644 index 00000000..fb2db80a --- /dev/null +++ b/src/src/Environment/Docker.php @@ -0,0 +1,34 @@ +cache = $cache; + $this->environment_monitor = $environment_monitor; + $this->filesystem = $filesystem; + $this->docker = $docker; + } + + public static function get_name(): string { + return 'e2e'; + } + + public function up(): void { + $this->maybe_download(); + + $cache_dir = Config::get_qit_dir() . '/cache'; + $source_environment = Config::get_qit_dir() . '/environments/' . self::get_name(); + $temporary_environment = Config::get_qit_dir() . '/temporary-envs/' . self::get_name() . uniqid(); + + if ( ! file_exists( $cache_dir ) ) { + mkdir( $cache_dir ); + } + + if ( ! file_exists( Config::get_qit_dir() . '/temporary-envs' ) ) { + mkdir( Config::get_qit_dir() . '/temporary-envs' ); + } + + $start = microtime( true ); + + // Copy the reference environment to a temporary one. + $this->filesystem->mirror( $source_environment, $temporary_environment ); + + if ( ! file_exists( $temporary_environment . '/docker-compose-generator.php' ) ) { + throw new \RuntimeException( "Failed to copy the environment." ); + } + + $env_info = new EnvInfo(); + $env_info->type = self::get_name(); + $env_info->temporary_env = $temporary_environment; + $env_info->created_at = time(); + $env_info->status = 'pending'; + + $this->environment_monitor->environment_added_or_updated( $env_info ); + + // Generate docker-compose.yml in the temporary environment. + passthru( "CACHE_DIR=$cache_dir " . PHP_BINARY . ' ' . $temporary_environment . '/docker-compose-generator.php' ); + + $docker_compose = $this->docker->find_docker_compose(); + + // Start docker compose in temporary environment. + exec( $docker_compose . ' -f ' . $temporary_environment . '/docker-compose.yml up -d', $up_output, $up_result_code ); + + if ( $up_result_code !== 0 ) { + exec( $docker_compose . ' -f ' . $temporary_environment . '/docker-compose.yml down', $down_output, $down_result_code ); + + $this->filesystem->remove( $temporary_environment ); + + if ( file_exists( $temporary_environment . '/docker-compose-generator.php' ) ) { + App::make( Output::class )->writeln( sprintf( 'Failed to delete the temporary environment: %s', $temporary_environment ) ); + } + + $this->environment_monitor->environment_stopped( $env_info ); + + throw new \RuntimeException( "Failed to start the environment." ); + } + + $this->environment_monitor->environment_added_or_updated( $env_info ); + + $server_started = microtime( true ); + echo "Server started at " . round( microtime( true ) - $start, 2 ) . " seconds\n"; + + echo "Temporary environment: $temporary_environment\n"; + } + + public function down( string $env_info_id ) { + // Stops the given environment. + $docker_compose = $this->docker->find_docker_compose(); + + $env_info = $this->environment_monitor->get_env_info_by_id( $env_info_id ); + + exec( $docker_compose . ' -f ' . $env_info->temporary_env . '/docker-compose.yml down', $down_output, $down_result_code ); + $this->environment_monitor->environment_stopped( $env_info ); + } + + protected function maybe_download(): void { + $backend_hashes = $this->cache->get_manager_sync_data( 'environments' ); + + if ( ! isset( $backend_hashes[ self::get_name() ]['checksum'] ) || ! isset( $backend_hashes[ self::get_name() ]['url'] ) ) { + throw new \RuntimeException( 'E2E environment not set or incomplete.' ); + } + + if ( $this->cache->get( 'e2e_environment_hash' ) !== $backend_hashes[ self::get_name() ]['checksum'] ) { + if ( ! file_exists( Config::get_qit_dir() . '/environments' ) ) { + mkdir( Config::get_qit_dir() . '/environments' ); + } + // Download the environment. + file_put_contents( sprintf( Config::get_qit_dir() . '/environments/%s.zip', self::get_name() ), file_get_contents( $backend_hashes[ self::get_name() ]['url'] ) ); + + // Extract the environment. + $zip = new \ZipArchive(); + $zip->open( sprintf( Config::get_qit_dir() . '/environments/%s.zip', self::get_name() ) ); + $zip->extractTo( Config::get_qit_dir() . '/environments/' . self::get_name() ); + $zip->close(); + + $this->cache->set( 'e2e_environment_hash', $backend_hashes[ self::get_name() ], MONTH_IN_SECONDS ); + } + } +} \ No newline at end of file diff --git a/src/src/Environment/EnvInfo.php b/src/src/Environment/EnvInfo.php new file mode 100644 index 00000000..0f0af0b2 --- /dev/null +++ b/src/src/Environment/EnvInfo.php @@ -0,0 +1,34 @@ + $value ) { + if ( property_exists( $env_info, $key ) ) { + $env_info->$key = $value; + } + } + + return $env_info; + } + + public function get_id(): string { + return md5( $this->temporary_env ); + } +} \ No newline at end of file diff --git a/src/src/Environment/Environment.php b/src/src/Environment/Environment.php new file mode 100644 index 00000000..0eea6973 --- /dev/null +++ b/src/src/Environment/Environment.php @@ -0,0 +1,13 @@ +cache = $cache; + } + + public function get(): array { + $this->environment_monitor = $this->cache->get( 'environment_monitor' ) ?? []; + + foreach ( $this->environment_monitor as $key => $env_info ) { + if ( is_array( $env_info ) ) { + $this->environment_monitor[ $key ] = EnvInfo::from_array( $env_info ); + } + } + + return $this->environment_monitor; + } + + public function get_env_info_by_id( string $env_info_id ) { + $environments = $this->get(); + + /** @var EnvInfo $env_info */ + foreach ( $environments as $env_info ) { + if ( $env_info->get_id() === $env_info_id ) { + return $env_info; + } + } + + throw new \Exception( 'Environment not found.' ); + } + + public function get_env_info_by_path( string $temporary_path ) { + $environments = $this->get(); + + foreach ( $environments as $env_info ) { + if ( $env_info->temporary_env === $temporary_path ) { + return $env_info; + } + } + + throw new \Exception( 'Environment not found.' ); + } + + public function environment_added_or_updated( EnvInfo $env_info ): bool { + $environments = $this->get(); + $environments[ $env_info->get_id() ] = $env_info; + $this->cache->set( 'environment_monitor', $environments, WEEK_IN_SECONDS ); + + return true; + } + + public function environment_stopped( EnvInfo $env_info ): bool { + $this->cache->set( 'environment_monitor', array_diff_key( $this->get(), [ $env_info->get_id() => true ] ), WEEK_IN_SECONDS ); + + return true; + } +} \ No newline at end of file diff --git a/src/src/bootstrap.php b/src/src/bootstrap.php index 1a6b273f..205a7d31 100644 --- a/src/src/bootstrap.php +++ b/src/src/bootstrap.php @@ -10,6 +10,10 @@ use QIT_CLI\Commands\Backend\CurrentBackend; use QIT_CLI\Commands\Backend\RemoveBackend; use QIT_CLI\Commands\Backend\SwitchBackend; +use QIT_CLI\Commands\Environment\DownEnvironmentCommand; +use QIT_CLI\Commands\Environment\ListEnvironmentCommand; +use QIT_CLI\Commands\Environment\RestartEnvironmentCommand; +use QIT_CLI\Commands\Environment\UpEnvironmentCommand; use QIT_CLI\Commands\GetCommand; use QIT_CLI\Commands\ListCommand; use QIT_CLI\Commands\OnboardingCommand; @@ -41,6 +45,13 @@ App::setVar( 'CLI_VERSION', '@QIT_CLI_VERSION@' ); +define( 'MINUTE_IN_SECONDS', 60 ); +define( 'HOUR_IN_SECONDS', 60 * MINUTE_IN_SECONDS ); +define( 'DAY_IN_SECONDS', 24 * HOUR_IN_SECONDS ); +define( 'WEEK_IN_SECONDS', 7 * DAY_IN_SECONDS ); +define( 'MONTH_IN_SECONDS', 30 * DAY_IN_SECONDS ); +define( 'YEAR_IN_SECONDS', 365 * DAY_IN_SECONDS ); + // Initialize Console. $application = new class( 'Quality Insights Toolkit CLI', App::getVar( 'CLI_VERSION' ) ) extends Application { // Expose protected method. @@ -160,6 +171,7 @@ public function filter( $in, $out, &$consumed, $closing ): int { $application->add( $container->make( ConfigDirCommand::class ) ); $application->add( $container->make( OnboardingCommand::class ) ); + // Partner commands. $application->add( $container->make( AddPartner::class ) ); @@ -213,6 +225,16 @@ public function filter( $in, $out, &$consumed, $closing ): int { // Dynamically crete Mass Test run command. $container->make( CreateMassTestCommands::class )->register_commands( $application ); } + + // Environment commands. + try { + $application->add( $container->make( UpEnvironmentCommand::class ) ); + $application->add( $container->make( DownEnvironmentCommand::class ) ); + $application->add( $container->make( RestartEnvironmentCommand::class ) ); + $application->add( $container->make( ListEnvironmentCommand::class ) ); + } catch ( \Exception $e ) { + App::make( Output::class )->writeln( $e->getMessage() ); + } } if ( $container->make( Output::class )->isVerbose() ) { diff --git a/src/src/helpers.php b/src/src/helpers.php index c422ec77..335b6f26 100644 --- a/src/src/helpers.php +++ b/src/src/helpers.php @@ -132,3 +132,24 @@ function generate_uuid4() { mt_rand( 0, 0xffff ) ); } + +/** + * @param int $seconds The number of seconds to format. + * + * @return string A human-readable string representing the elapsed time. + */ +function format_elapsed_time( int $seconds ): string { + $periods = [ 'second', 'minute', 'hour', 'day', 'week', 'month', 'year' ]; + $lengths = [ 60, 60, 24, 7, 4.35, 12 ]; + + for ( $i = 0; $seconds >= $lengths[ $i ] && $i < count( $lengths ) - 1; $i ++ ) { + $seconds /= $lengths[ $i ]; + } + + $seconds = round( $seconds ); + if ( $seconds != 1 ) { + $periods[ $i ] .= 's'; + } + + return "$seconds $periods[$i] ago"; +} \ No newline at end of file diff --git a/src/tests/EnvironmentMonitorTest.php b/src/tests/EnvironmentMonitorTest.php new file mode 100644 index 00000000..68da63b5 --- /dev/null +++ b/src/tests/EnvironmentMonitorTest.php @@ -0,0 +1,86 @@ +type = 'e2e'; + $env_info->temporary_env = "/path/to/temporary-envs/$identifier"; + $env_info->created_at = 1708728299; + $env_info->status = 'pending'; + + return $env_info; + } + + public function test_environment_add() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $environment_monitor->environment_added_or_updated( $this->make_env_info() ); + + $this->assertMatchesJsonSnapshot( $environment_monitor->get() ); + } + + public function test_environment_adds_multiple() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $environment_monitor->environment_added_or_updated( $this->make_env_info() ); + $environment_monitor->environment_added_or_updated( $this->make_env_info( 'bar' ) ); + + $this->assertMatchesJsonSnapshot( $environment_monitor->get() ); + } + + public function test_environment_stopped() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $environment_monitor->environment_added_or_updated( $this->make_env_info() ); + $environment_monitor->environment_stopped( $this->make_env_info() ); + + $this->assertMatchesJsonSnapshot( $environment_monitor->get() ); + } + + public function test_environment_updated() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $environment_monitor->environment_added_or_updated( $this->make_env_info() ); + + $updated = $this->make_env_info(); + $updated->status = 'running'; + $environment_monitor->environment_added_or_updated( $updated ); + + $this->assertMatchesJsonSnapshot( $environment_monitor->get() ); + } + + public function test_environment_stops_with_multiple() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $environment_monitor->environment_added_or_updated( $this->make_env_info() ); + + $updated = $this->make_env_info( 'bar' ); + $updated->status = 'running'; + $environment_monitor->environment_added_or_updated( $updated ); + + $environment_monitor->environment_stopped( $this->make_env_info() ); + + $this->assertMatchesJsonSnapshot( $environment_monitor->get() ); + } + + public function test_makes_env_info_from_path() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $environment_monitor->environment_added_or_updated( $this->make_env_info() ); + + $env_info = $environment_monitor->get_env_info_by_path( '/path/to/temporary-envs/foo' ); + + $this->assertMatchesJsonSnapshot( (array) $env_info ); + } + + public function test_makes_env_info_from_id() { + $environment_monitor = App::make( EnvironmentMonitor::class ); + $env_info = $this->make_env_info(); + $environment_monitor->environment_added_or_updated( $env_info ); + + $env_info2 = $environment_monitor->get_env_info_by_id( $env_info->get_id() ); + + $this->assertEquals( $env_info, $env_info2 ); + } +} diff --git a/src/tests/RestartEnvironmentTest.php b/src/tests/RestartEnvironmentTest.php new file mode 100644 index 00000000..9834ce6d --- /dev/null +++ b/src/tests/RestartEnvironmentTest.php @@ -0,0 +1,74 @@ +application = new Application(); + + $this->application->add( App::make( UpEnvironmentCommand::class ) ); + $this->application->add( App::make( DownEnvironmentCommand::class ) ); + $this->application->add( App::make( RestartEnvironmentCommand::class ) ); + + $this->restart_command_tester = new CommandTester( $this->application->find( RestartEnvironmentCommand::getDefaultName() ) ); + } + + public function testRestartEnvironment() { + $this->restart_command_tester->execute( [], [ 'verbosity' => \Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE ] ); + + $this->assertMatchesSnapshot( $this->restart_command_tester->getDisplay() ); + } + + public function testRestartEnvironmentWithOptions() { + App::make( UpEnvironmentCommand::class )->run( new ArrayInput( [ + '--php_version' => '8.2', + ] ), new NullOutput() ); + $this->restart_command_tester->execute( [], [ 'verbosity' => \Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE ] ); + + $this->assertMatchesSnapshot( $this->restart_command_tester->getDisplay() ); + } + + public function testRestartEnvironmentWithMultipleOptions() { + App::make( UpEnvironmentCommand::class )->run( new ArrayInput( [ + '--php_version' => '8.1', + '--wordpress_version' => 'rc', + '--woocommerce_version' => 'stable', + ] ), new NullOutput() ); + $this->restart_command_tester->execute( [], [ 'verbosity' => \Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE ] ); + + $this->assertMatchesSnapshot( $this->restart_command_tester->getDisplay() ); + } + + public function testRestartEnvironmentMultipleTimes() { + App::make( UpEnvironmentCommand::class )->run( new ArrayInput( [ + '--php_version' => '8.1', + '--wordpress_version' => 'rc', + '--woocommerce_version' => 'stable', + ] ), new NullOutput() ); + + $this->restart_command_tester->execute( [], [ 'verbosity' => \Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE ] ); + + App::make( UpEnvironmentCommand::class )->run( new ArrayInput( [ + '--php_version' => '8.0', + '--wordpress_version' => 'stable', + '--woocommerce_version' => 'stable', + ] ), new NullOutput() ); + + $this->restart_command_tester->execute( [], [ 'verbosity' => \Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE ] ); + + $this->assertMatchesSnapshot( $this->restart_command_tester->getDisplay() ); + } +} diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__testEnvironmentUpdate__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__testEnvironmentUpdate__1.json new file mode 100644 index 00000000..71d6f2c0 --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__testEnvironmentUpdate__1.json @@ -0,0 +1,8 @@ +{ + "e73bdf327f11823203f80f4f4542cdf3": { + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/e2e", + "created_at": 1708728299, + "status": "pending" + } +} diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_add__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_add__1.json new file mode 100644 index 00000000..9be1634c --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_add__1.json @@ -0,0 +1,8 @@ +{ + "944daf0ddaaa9d7d9270b6ecacc8f83e": { + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/foo", + "created_at": 1708728299, + "status": "pending" + } +} diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_adds_multiple__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_adds_multiple__1.json new file mode 100644 index 00000000..f91dc21f --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_adds_multiple__1.json @@ -0,0 +1,14 @@ +{ + "944daf0ddaaa9d7d9270b6ecacc8f83e": { + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/foo", + "created_at": 1708728299, + "status": "pending" + }, + "d7714310314d312ed7809de78d6fea27": { + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/bar", + "created_at": 1708728299, + "status": "pending" + } +} diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stopped__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stopped__1.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stopped__1.json @@ -0,0 +1 @@ +[] diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stops_with_multiple__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stops_with_multiple__1.json new file mode 100644 index 00000000..c2dfb320 --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_stops_with_multiple__1.json @@ -0,0 +1,8 @@ +{ + "d7714310314d312ed7809de78d6fea27": { + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/bar", + "created_at": 1708728299, + "status": "running" + } +} diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_updated__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_updated__1.json new file mode 100644 index 00000000..374207ac --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__test_environment_updated__1.json @@ -0,0 +1,8 @@ +{ + "944daf0ddaaa9d7d9270b6ecacc8f83e": { + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/foo", + "created_at": 1708728299, + "status": "running" + } +} diff --git a/src/tests/__snapshots__/EnvironmentMonitorTest__test_makes_env_info_from_path__1.json b/src/tests/__snapshots__/EnvironmentMonitorTest__test_makes_env_info_from_path__1.json new file mode 100644 index 00000000..b1ab1f69 --- /dev/null +++ b/src/tests/__snapshots__/EnvironmentMonitorTest__test_makes_env_info_from_path__1.json @@ -0,0 +1,6 @@ +{ + "type": "e2e", + "temporary_env": "\/path\/to\/temporary-envs\/foo", + "created_at": 1708728299, + "status": "pending" +} diff --git a/src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironmentMultipleTimes__1.php b/src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironmentMultipleTimes__1.php new file mode 100644 index 00000000..c3e42527 --- /dev/null +++ b/src/tests/__snapshots__/RestartEnvironmentTest__testRestartEnvironmentMultipleTimes__1.php @@ -0,0 +1,4 @@ +