├── .docker ├── php7.3 │ └── Dockerfile ├── php7.4 │ └── Dockerfile ├── php8.0 │ └── Dockerfile ├── php8.1 │ └── Dockerfile └── php8.2 │ └── Dockerfile ├── .gitattributes ├── .github └── workflows │ └── continuous-integration.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── MIGRATION.md ├── Makefile ├── README.md ├── Vagrantfile ├── composer.json ├── docker-compose.yml ├── phpcs.xml ├── phpstan.neon ├── phpunit.xml ├── src ├── Command │ ├── CommandDispatcherInterface.php │ ├── CommandInterface.php │ ├── SymfonyCommand.php │ └── SymfonyCommandDispatcher.php ├── Exception │ ├── CommandFailedException.php │ ├── ExceptionInterface.php │ ├── UnitNotFoundException.php │ └── UnitTypeNotSupportedException.php ├── SystemCtl.php ├── Unit │ ├── AbstractUnit.php │ ├── Automount.php │ ├── Device.php │ ├── Mount.php │ ├── Scope.php │ ├── Service.php │ ├── Slice.php │ ├── Socket.php │ ├── Swap.php │ ├── Target.php │ ├── Timer.php │ └── UnitInterface.php └── Utils │ └── OutputFetcher.php └── test ├── Integration ├── Command │ └── SymfonyCommandDispatcherTest.php ├── SystemCtlTest.php └── Unit │ └── UnitTest.php └── Unit ├── Command └── SymfonyCommandTest.php ├── SystemCtlTest.php ├── Unit ├── AbstractUnitTest.php └── UnitStub.php └── Utils └── OutputFetcherTest.php /.docker/php7.3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3-alpine 2 | 3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/ 4 | 5 | COPY --from=composer/composer:latest-bin /composer /usr/bin/composer 6 | ENV COMPOSER_ALLOW_SUPERUSER 1 7 | 8 | WORKDIR /docker 9 | # Workaround to keep container running 10 | CMD ["tail", "-f", "/dev/null"] 11 | -------------------------------------------------------------------------------- /.docker/php7.4/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.4-alpine 2 | 3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/ 4 | 5 | COPY --from=composer/composer:latest-bin /composer /usr/bin/composer 6 | ENV COMPOSER_ALLOW_SUPERUSER 1 7 | 8 | WORKDIR /docker 9 | # Workaround to keep container running 10 | CMD ["tail", "-f", "/dev/null"] 11 | -------------------------------------------------------------------------------- /.docker/php8.0/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.0-alpine 2 | 3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/ 4 | 5 | COPY --from=composer/composer:latest-bin /composer /usr/bin/composer 6 | ENV COMPOSER_ALLOW_SUPERUSER 1 7 | 8 | WORKDIR /docker 9 | # Workaround to keep container running 10 | CMD ["tail", "-f", "/dev/null"] 11 | -------------------------------------------------------------------------------- /.docker/php8.1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-alpine 2 | 3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/ 4 | 5 | COPY --from=composer/composer:latest-bin /composer /usr/bin/composer 6 | ENV COMPOSER_ALLOW_SUPERUSER 1 7 | 8 | WORKDIR /docker 9 | # Workaround to keep container running 10 | CMD ["tail", "-f", "/dev/null"] 11 | -------------------------------------------------------------------------------- /.docker/php8.2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.2.0RC5-alpine 2 | 3 | COPY --from=mlocati/php-extension-installer /usr/bin/install-php-extensions /usr/bin/ 4 | 5 | COPY --from=composer/composer:latest-bin /composer /usr/bin/composer 6 | ENV COMPOSER_ALLOW_SUPERUSER 1 7 | 8 | WORKDIR /docker 9 | # Workaround to keep container running 10 | CMD ["tail", "-f", "/dev/null"] 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: CI Pipeline 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: 6 | - "[0-9]+.[0-9]+.x" 7 | pull_request: 8 | 9 | jobs: 10 | 11 | coding-standard: 12 | name: "Coding Standard" 13 | runs-on: "${{ matrix.operating-system }}" 14 | strategy: 15 | fail-fast: true 16 | matrix: 17 | operating-system: ['ubuntu-latest'] 18 | php-version: ['8.1'] 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php-version }} 27 | tools: "composer:v2" 28 | 29 | - name: Install dependencies 30 | run: composer install --no-progress --prefer-dist --optimize-autoloader 31 | 32 | - name: Check codestyle 33 | run: vendor/bin/phpcs -s 34 | 35 | static-analysis: 36 | name: "Static Analysis" 37 | runs-on: "${{ matrix.operating-system }}" 38 | 39 | strategy: 40 | fail-fast: true 41 | matrix: 42 | php-version: ['8.1'] 43 | operating-system: ['ubuntu-latest'] 44 | 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v2 48 | 49 | - name: Setup PHP 50 | uses: shivammathur/setup-php@v2 51 | with: 52 | php-version: ${{ matrix.php-version }} 53 | tools: "composer:v2" 54 | 55 | - name: Install dependencies 56 | run: composer install --no-progress --prefer-dist --optimize-autoloader 57 | 58 | - name: Analyze code with static-analysis 59 | run: vendor/bin/phpstan analyse --no-progress 60 | 61 | unit-tests: 62 | name: "Unit Tests" 63 | 64 | runs-on: "${{ matrix.operating-system }}" 65 | continue-on-error: "${{ matrix.experimental }}" 66 | 67 | strategy: 68 | fail-fast: false 69 | matrix: 70 | php-version: ["8.1","8.2"] 71 | operating-system: ["ubuntu-latest"] 72 | experimental: [false] 73 | 74 | steps: 75 | - name: Checkout 76 | uses: actions/checkout@v2 77 | 78 | - name: Setup PHP 79 | uses: shivammathur/setup-php@v2 80 | with: 81 | php-version: ${{ matrix.php-version }} 82 | tools: "composer:v2" 83 | 84 | - name: Install dependencies 85 | run: composer install --no-progress --prefer-dist --optimize-autoloader --ignore-platform-req=php 86 | 87 | - name: Execute tests 88 | run: vendor/bin/phpunit --colors=always --coverage-text 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer files 2 | composer.phar 3 | vendor/ 4 | composer.lock 5 | 6 | # PHPUnit 7 | build/ 8 | .phpunit.result.cache 9 | 10 | # Vagrant 11 | .vagrant 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | ## [0.8.2] - 2021-05-27 6 | ### Added 7 | - Added PHP8 Support ([#48](https://github.com/icanhazstring/systemctl-php/pull/48)) (thanks to [@marcingy](https://github.com/marcingy)) 8 | 9 | ## [0.8.1] - 2020-11-09 10 | ### Added 11 | - Added `Target` unit ([#38](https://github.com/icanhazstring/systemctl-php/pull/38)) (thanks to [@peter279k](https://github.com/peter279k)) 12 | - Added `Swap` unit ([#39](https://github.com/icanhazstring/systemctl-php/pull/39)) (thanks to [@peter279k](https://github.com/peter279k)) 13 | - Added `Automount` unit ([#40](https://github.com/icanhazstring/systemctl-php/pull/40)) (thanks to [@peter279k](https://github.com/peter279k)) 14 | - Added `Mount` unit ([#41](https://github.com/icanhazstring/systemctl-php/pull/41)) (thanks to [@peter279k](https://github.com/peter279k)) 15 | 16 | ### Changed 17 | - Replace Travis CI with GitHub Action status badge ([#44](https://github.com/icanhazstring/systemctl-php/pull/44)) (thanks to [@peter279k](https://github.com/peter279k)) 18 | - Migrate `phpunit.xml` for new version ([#42](https://github.com/icanhazstring/systemctl-php/pull/42)) (thanks to [@peter279k](https://github.com/peter279k)) 19 | 20 | ## [0.8.0] - 2020-11-05 21 | ### Added 22 | - Added `Slice` unit ([#36](https://github.com/icanhazstring/systemctl-php/pull/36)) (thanks to [@peter279k](https://github.com/peter279k)) 23 | - Added method `SystemCtl::reset-failed()` ([#37](https://github.com/icanhazstring/systemctl-php/pull/37)) (thanks to [@icanhazstring](https://github.com/icanhazstring)) 24 | 25 | ### Changed 26 | - Dropped support for php7.2 27 | - Dropped support for `symfony/process:^4.4` 28 | 29 | ## [0.7.1] - 2020-05-27 30 | ### Added 31 | - Added `Scope` unit ([#32](https://github.com/icanhazstring/systemctl-php/pull/32)) (thanks to [@peter279k](https://github.com/peter279k)) 32 | 33 | ## [0.7.0] - 2020-02-16 34 | ### Changed 35 | - Moved classes to different namespace (`SystemCtl` to `icanhazstring\Systemctl`) 36 | - Dropped support for PHP7.1 37 | - Dropped support for `symfony/process:^3.x` 38 | 39 | ### Added 40 | - Added `CHANGELOG.md` and `MIGRATION.md` 41 | - Added support for `symfony/process:^4.4 || ^5.0` 42 | - Added code quality tools 43 | 44 | ## Previous releases 45 | - No changelog available 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2020 Andreas Frömer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migration 2 | 3 | ## v0.6.x to 0.7.0 4 | The namespace `SystemCtl\` was moved to `icanhazstring\SystemCtl`. 5 | You can do a search/replace to move you implementation. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CONTAINER=systemctl-php-7.4 2 | 3 | up: 4 | docker-compose up -d 5 | 6 | down: 7 | docker-compose down 8 | 9 | install: 10 | docker exec -it $(CONTAINER) composer install 11 | 12 | update: 13 | docker exec -it $(CONTAINER) composer update 14 | 15 | require: 16 | docker exec -it $(CONTAINER) composer require $(filter-out $@, $(MAKECMDGOALS)) 17 | 18 | remove: 19 | docker exec -it $(CONTAINER) composer remove $(filter-out $@, $(MAKECMDGOALS)) 20 | 21 | check: csfix cs phpunit analyse 22 | 23 | phpunit: 24 | docker exec -it $(CONTAINER) vendor/bin/phpunit 25 | 26 | analyse: 27 | docker exec -it $(CONTAINER) vendor/bin/phpstan analyse 28 | 29 | cs: 30 | docker exec -it $(CONTAINER) vendor/bin/phpcs 31 | 32 | csfix: 33 | docker exec -it $(CONTAINER) vendor/bin/phpcbf 34 | 35 | %: 36 | @true 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # systemctl-php 2 | ![CI Pipeline](https://github.com/icanhazstring/systemctl-php/workflows/CI%20Pipeline/badge.svg) [![Code Climate](https://codeclimate.com/github/icanhazstring/systemctl-php/badges/gpa.svg)](https://codeclimate.com/github/icanhazstring/systemctl-php) [![Test Coverage](https://codeclimate.com/github/icanhazstring/systemctl-php/badges/coverage.svg)](https://codeclimate.com/github/icanhazstring/systemctl-php/coverage) [![Join the chat at https://gitter.im/icanhazstring/systemctl-php](https://badges.gitter.im/icanhazstring/systemctl-php.svg)](https://gitter.im/icanhazstring/systemctl-php?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 3 | 4 | PHP wrapper for systemctl 5 | 6 | # How to install 7 | ```php 8 | $ composer require icanhazstring/systemctl-php 9 | ``` 10 | 11 | ## Current supported units 12 | See [Units](src/Unit) 13 | 14 | > If you like to add support for more units, feel free to grab an issue and contribute. 15 | 16 | ## Current supported commands 17 | - start 18 | - stop 19 | - enable 20 | - disable 21 | - reload 22 | - restart 23 | - isEnabled 24 | - isActive 25 | 26 | > If you like to add support for more commands, feel free to contribute. 27 | 28 | ## How to change the binary 29 | 30 | ```php 31 | SystemCtl::setBinary('/bin/systemctl'); 32 | ``` 33 | 34 | ## How to change command timeout 35 | To change command tmeout simply call the static method `setTimeout`. 36 | ```php 37 | SystemCtl::setTimeout(10); 38 | ``` 39 | 40 | > The default timeout is set to `3` seconds 41 | 42 | ## "I need sudo to run commands" 43 | If you need sudo, you should execute the bin executable with sudo. 44 | The incode support was dropped due to security reason. 45 | 46 | ## How do I start/stop/restart a unit? 47 | Simply is that. First we instantiate a `SystemCtl` instance an load a unit from a specific type. Here we use a `Service`. You will always get back `true` if the command succeeded. Otherwise the method will throw a `CommandFailedException`. 48 | 49 | ```php 50 | $systemCtl = new SystemCtl(); 51 | 52 | // start/stop/enable/disable/reload/restart 53 | $systemCtl->getService('nginx')->start(); 54 | $systemCtl->getService('nginx')->stop(); 55 | ``` 56 | 57 | # How to Contribute 58 | Clone the repo and install using `composer` 59 | 60 | ```bash 61 | $ composer install 62 | ``` 63 | 64 | Make your changes and make sure you run *test*, *codesniffer* and *phpstan*. 65 | 66 | ```bash 67 | $ composer test 68 | > vendor/bin/phpunit 69 | PHPUnit 9.5.3 by Sebastian Bergmann and contributors. 70 | 71 | ............................................................... 63 / 128 ( 49%) 72 | ............................................................... 126 / 128 ( 98%) 73 | .. 128 / 128 (100%) 74 | 75 | Time: 00:00.033, Memory: 10.00 MB 76 | 77 | OK (128 tests, 192 assertions) 78 | 79 | $ composer cs 80 | > vendor/bin/phpcs --standard=PSR2 src/ && vendor/bin/phpcs --standard=PSR2 tests/ 81 | 82 | $ 83 | 84 | $ composer analyse 85 | > vendor/bin/phpstan analyse --no-progress 86 | Note: Using configuration file /data/systemctl-php/phpstan.neon. 87 | 88 | 89 | [OK] No errors 90 | 91 | $ 92 | ``` 93 | 94 | # Credits 95 | This library is heavily influenced by [@mjanser](https://github.com/mjanser) [php-systemctl](https://github.com/mjanser/php-systemctl). 96 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | Vagrant.configure("2") do |config| 2 | config.vm.box = "puphpet/ubuntu1604-x64" 3 | 4 | config.vm.provision "shell", inline: <<-SHELL 5 | add-apt-repository ppa:ondrej/php 6 | apt-get update 7 | apt-get install -y php7.2 php7.2-xml php7.2-mbstring php7.2-zip php7.2-curl php7.2-xdebug composer 8 | 9 | # Switch to /vagrant and install packages 10 | cd /vagrant && composer install 11 | SHELL 12 | end 13 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "icanhazstring/systemctl-php", 3 | "description": "PHP wrapper for systemctl", 4 | "type": "library", 5 | "require": { 6 | "php": "^7.3|^8.0", 7 | "symfony/process": "^5.0|^6.0" 8 | }, 9 | "require-dev": { 10 | "phpunit/phpunit": "^9.6.9", 11 | "squizlabs/php_codesniffer": "^3.7.2", 12 | "slevomat/coding-standard": "^6.4.1", 13 | "phpstan/phpstan": "^1.10.21", 14 | "phpspec/prophecy-phpunit": "^2.0.2" 15 | }, 16 | "autoload": { 17 | "psr-4": { 18 | "icanhazstring\\SystemCtl\\": "src/" 19 | } 20 | }, 21 | "autoload-dev": { 22 | "psr-4": { 23 | "icanhazstring\\SystemCtl\\Test\\": "test/" 24 | } 25 | }, 26 | "license": "MIT", 27 | "authors": [ 28 | { 29 | "name": "icanhazstring", 30 | "email": "blubb0r05+github@gmail.com" 31 | } 32 | ], 33 | "extra": { 34 | "branch-alias": { 35 | "dev-master": "0.8.x-dev" 36 | } 37 | }, 38 | "scripts": { 39 | "analyse": "vendor/bin/phpstan analyse --no-progress", 40 | "test": "vendor/bin/phpunit", 41 | "cs": "vendor/bin/phpcs" 42 | }, 43 | "config": { 44 | "allow-plugins": { 45 | "dealerdirect/phpcodesniffer-composer-installer": true 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | php7.3: 5 | build: .docker/php7.3 6 | container_name: systemctl-php-7.3 7 | volumes: 8 | - .:/docker:rw 9 | tty: true 10 | 11 | php7.4: 12 | build: .docker/php7.4 13 | container_name: systemctl-php-7.4 14 | volumes: 15 | - .:/docker:rw 16 | tty: true 17 | 18 | php8.0: 19 | build: .docker/php8.0 20 | container_name: systemctl-php-8.0 21 | volumes: 22 | - .:/docker:rw 23 | tty: true 24 | 25 | php8.1: 26 | build: .docker/php8.1 27 | container_name: systemctl-php-8.1 28 | volumes: 29 | - .:/docker:rw 30 | tty: true 31 | 32 | php8.2: 33 | build: .docker/php8.2 34 | container_name: systemctl-php-8.2 35 | volumes: 36 | - .:/docker:rw 37 | tty: true 38 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | src 21 | test 22 | 23 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | inferPrivatePropertyTypeFromConstructor: true 3 | 4 | level: max 5 | paths: 6 | - src/ 7 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./src 6 | 7 | 8 | 9 | 10 | ./test/Unit 11 | 12 | 13 | ./test/Integration 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Command/CommandDispatcherInterface.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | interface CommandInterface 14 | { 15 | /** 16 | * @return CommandInterface 17 | * @throws CommandFailedException 18 | */ 19 | public function run(): CommandInterface; 20 | 21 | /** 22 | * @return string 23 | */ 24 | public function getOutput(): string; 25 | 26 | /** 27 | * @return bool 28 | */ 29 | public function isSuccessful(): bool; 30 | } 31 | -------------------------------------------------------------------------------- /src/Command/SymfonyCommand.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class SymfonyCommand implements CommandInterface 15 | { 16 | /** @var Process */ 17 | private $process; 18 | 19 | /** 20 | * @param Process $process 21 | */ 22 | public function __construct(Process $process) 23 | { 24 | $this->process = $process; 25 | } 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | public function getOutput(): string 31 | { 32 | return $this->process->getOutput(); 33 | } 34 | 35 | /** 36 | * @inheritdoc 37 | */ 38 | public function isSuccessful(): bool 39 | { 40 | return $this->process->isSuccessful(); 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function run(): CommandInterface 47 | { 48 | $this->process->run(); 49 | 50 | if (!$this->process->isSuccessful()) { 51 | throw new CommandFailedException($this->process->getErrorOutput()); 52 | } 53 | 54 | return $this; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Command/SymfonyCommandDispatcher.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class SymfonyCommandDispatcher implements CommandDispatcherInterface 14 | { 15 | /** @var string */ 16 | private $binary; 17 | /** @var int */ 18 | private $timeout; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public function setBinary(string $binary): CommandDispatcherInterface 24 | { 25 | $this->binary = $binary; 26 | 27 | return $this; 28 | } 29 | 30 | /** 31 | * @inheritdoc 32 | */ 33 | public function setTimeout(int $timeout): CommandDispatcherInterface 34 | { 35 | $this->timeout = $timeout; 36 | 37 | return $this; 38 | } 39 | 40 | public function dispatch(string ...$commands): CommandInterface 41 | { 42 | $process = new Process(array_merge([$this->binary], $commands)); 43 | $process->setTimeout($this->timeout); 44 | 45 | $process = new SymfonyCommand($process); 46 | 47 | return $process->run(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Exception/CommandFailedException.php: -------------------------------------------------------------------------------- 1 | setTimeout(self::$timeout) 94 | ->setBinary(self::$binary); 95 | 96 | // @phpstan-ignore-next-line 97 | return new $unitClass($unitName, $commandDispatcher); 98 | } 99 | 100 | /** 101 | * List all supported units 102 | * 103 | * @param null|string $unitPrefix 104 | * @param string[] $unitTypes 105 | * 106 | * @return array|string[] 107 | * @throws Exception\CommandFailedException 108 | */ 109 | public function listUnits(?string $unitPrefix = null, array $unitTypes = self::SUPPORTED_UNITS): array 110 | { 111 | $commands = ['list-units']; 112 | 113 | if ($unitPrefix) { 114 | $commands[] = $unitPrefix . '*'; 115 | } 116 | 117 | $output = $this->getCommandDispatcher()->dispatch(...$commands)->getOutput(); 118 | 119 | return array_reduce($unitTypes, function ($carry, $unitSuffix) use ($output) { 120 | $result = Utils\OutputFetcher::fetchUnitNames($unitSuffix, $output); 121 | 122 | return array_merge($carry, $result); 123 | }, []); 124 | } 125 | 126 | /** 127 | * @param string $name 128 | * 129 | * @return Service 130 | * @throws Exception\CommandFailedException 131 | */ 132 | public function getService(string $name): Service 133 | { 134 | $units = $this->listUnits($name, [Service::UNIT]); 135 | 136 | $unitName = $this->searchForUnitInUnits($name, $units); 137 | 138 | if ($unitName === null) { 139 | throw UnitNotFoundException::create(Service::UNIT, $name); 140 | } 141 | 142 | return new Service($unitName, $this->getCommandDispatcher()); 143 | } 144 | 145 | /** 146 | * @param string $unitName 147 | * @param array|string[] $units 148 | * 149 | * @return null|string 150 | */ 151 | protected function searchForUnitInUnits(string $unitName, array $units): ?string 152 | { 153 | foreach ($units as $unit) { 154 | if ($unit === $unitName) { 155 | return $unit; 156 | } 157 | } 158 | 159 | return null; 160 | } 161 | 162 | /** 163 | * @param null|string $unitPrefix 164 | * 165 | * @return Service[] 166 | */ 167 | public function getServices(?string $unitPrefix = null): array 168 | { 169 | $units = $this->listUnits($unitPrefix, [Service::UNIT]); 170 | 171 | return array_map(function ($unitName) { 172 | return new Service($unitName, $this->getCommandDispatcher()); 173 | }, $units); 174 | } 175 | 176 | /** 177 | * @param string $name 178 | * 179 | * @return Timer 180 | */ 181 | public function getTimer(string $name): Timer 182 | { 183 | $units = $this->listUnits($name, [Timer::UNIT]); 184 | 185 | $unitName = $this->searchForUnitInUnits($name, $units); 186 | 187 | if (is_null($unitName)) { 188 | throw UnitNotFoundException::create(Timer::UNIT, $name); 189 | } 190 | 191 | return new Timer($unitName, $this->getCommandDispatcher()); 192 | } 193 | 194 | /** 195 | * @param null|string $unitPrefix 196 | * 197 | * @return Timer[] 198 | */ 199 | public function getTimers(?string $unitPrefix = null): array 200 | { 201 | $units = $this->listUnits($unitPrefix, [Timer::UNIT]); 202 | 203 | return array_map(function ($unitName) { 204 | return new Timer($unitName, $this->getCommandDispatcher()); 205 | }, $units); 206 | } 207 | 208 | /** 209 | * @param string $name 210 | * 211 | * @return Socket 212 | */ 213 | public function getSocket(string $name): Socket 214 | { 215 | $units = $this->listUnits($name, [Socket::UNIT]); 216 | 217 | $unitName = $this->searchForUnitInUnits($name, $units); 218 | 219 | if (is_null($unitName)) { 220 | throw UnitNotFoundException::create(Socket::UNIT, $name); 221 | } 222 | 223 | return new Socket($unitName, $this->getCommandDispatcher()); 224 | } 225 | 226 | /** 227 | * @param null|string $unitPrefix 228 | * 229 | * @return Socket[] 230 | */ 231 | public function getSockets(?string $unitPrefix = null): array 232 | { 233 | $units = $this->listUnits($unitPrefix, [Socket::UNIT]); 234 | 235 | return array_map(function ($unitName) { 236 | return new Socket($unitName, $this->getCommandDispatcher()); 237 | }, $units); 238 | } 239 | 240 | /** 241 | * @param string $name 242 | * 243 | * @return Scope 244 | */ 245 | public function getScope(string $name): Scope 246 | { 247 | $units = $this->listUnits($name, [Scope::UNIT]); 248 | 249 | $unitName = $this->searchForUnitInUnits($name, $units); 250 | 251 | if (is_null($unitName)) { 252 | throw UnitNotFoundException::create(Scope::UNIT, $name); 253 | } 254 | 255 | return new Scope($unitName, $this->getCommandDispatcher()); 256 | } 257 | 258 | /** 259 | * @param null|string $unitPrefix 260 | * 261 | * @return Scope[] 262 | */ 263 | public function getScopes(?string $unitPrefix = null): array 264 | { 265 | $units = $this->listUnits($unitPrefix, [Scope::UNIT]); 266 | 267 | return array_map(function ($unitName) { 268 | return new Scope($unitName, $this->getCommandDispatcher()); 269 | }, $units); 270 | } 271 | 272 | /** 273 | * @param string $name 274 | * 275 | * @return Slice 276 | */ 277 | public function getSlice(string $name): Slice 278 | { 279 | $units = $this->listUnits($name, [Slice::UNIT]); 280 | 281 | $unitName = $this->searchForUnitInUnits($name, $units); 282 | 283 | if (is_null($unitName)) { 284 | throw UnitNotFoundException::create(Slice::UNIT, $name); 285 | } 286 | 287 | return new Slice($unitName, $this->getCommandDispatcher()); 288 | } 289 | 290 | /** 291 | * @param null|string $unitPrefix 292 | * 293 | * @return Slice[] 294 | */ 295 | public function getSlices(?string $unitPrefix = null): array 296 | { 297 | $units = $this->listUnits($unitPrefix, [Slice::UNIT]); 298 | 299 | return array_map(function ($unitName) { 300 | return new Slice($unitName, $this->getCommandDispatcher()); 301 | }, $units); 302 | } 303 | 304 | /** 305 | * @param string $name 306 | * 307 | * @return Target 308 | */ 309 | public function getTarget(string $name): Target 310 | { 311 | $units = $this->listUnits($name, [Target::UNIT]); 312 | 313 | $unitName = $this->searchForUnitInUnits($name, $units); 314 | 315 | if (is_null($unitName)) { 316 | throw UnitNotFoundException::create(Target::UNIT, $name); 317 | } 318 | 319 | return new Target($unitName, $this->getCommandDispatcher()); 320 | } 321 | 322 | /** 323 | * @param null|string $unitPrefix 324 | * 325 | * @return Target[] 326 | */ 327 | public function getTargets(?string $unitPrefix = null): array 328 | { 329 | $units = $this->listUnits($unitPrefix, [Target::UNIT]); 330 | 331 | return array_map(function ($unitName) { 332 | return new Target($unitName, $this->getCommandDispatcher()); 333 | }, $units); 334 | } 335 | 336 | /** 337 | * @param string $name 338 | * 339 | * @return Swap 340 | */ 341 | public function getSwap(string $name): Swap 342 | { 343 | $units = $this->listUnits($name, [Swap::UNIT]); 344 | 345 | $unitName = $this->searchForUnitInUnits($name, $units); 346 | 347 | if (is_null($unitName)) { 348 | throw UnitNotFoundException::create(Swap::UNIT, $name); 349 | } 350 | 351 | return new Swap($unitName, $this->getCommandDispatcher()); 352 | } 353 | 354 | /** 355 | * @param null|string $unitPrefix 356 | * 357 | * @return Swap[] 358 | */ 359 | public function getSwaps(?string $unitPrefix = null): array 360 | { 361 | $units = $this->listUnits($unitPrefix, [Swap::UNIT]); 362 | 363 | return array_map(function ($unitName) { 364 | return new Swap($unitName, $this->getCommandDispatcher()); 365 | }, $units); 366 | } 367 | 368 | /** 369 | * Restart the daemon to reload specs and new units 370 | * 371 | * @return bool 372 | * @throws Exception\CommandFailedException 373 | */ 374 | public function daemonReload(): bool 375 | { 376 | return $this->getCommandDispatcher()->dispatch('daemon-reload')->isSuccessful(); 377 | } 378 | 379 | /** 380 | * Reset failed state of all unit so they won't be listed using listUnits 381 | * 382 | * @return bool 383 | * @throws Exception\CommandFailedException 384 | */ 385 | public function resetFailed(): bool 386 | { 387 | return $this->getCommandDispatcher()->dispatch('reset-failed')->isSuccessful(); 388 | } 389 | 390 | /** 391 | * @return CommandDispatcherInterface 392 | */ 393 | public function getCommandDispatcher(): CommandDispatcherInterface 394 | { 395 | if ($this->commandDispatcher === null) { 396 | $this->commandDispatcher = (new SymfonyCommandDispatcher()) 397 | ->setTimeout(self::$timeout) 398 | ->setBinary(self::$binary); 399 | } 400 | 401 | return $this->commandDispatcher; 402 | } 403 | 404 | /** 405 | * @param CommandDispatcherInterface $dispatcher 406 | * 407 | * @return SystemCtl 408 | */ 409 | public function setCommandDispatcher(CommandDispatcherInterface $dispatcher) 410 | { 411 | $this->commandDispatcher = $dispatcher 412 | ->setTimeout(self::$timeout) 413 | ->setBinary(self::$binary); 414 | 415 | return $this; 416 | } 417 | 418 | /** 419 | * @param string $name 420 | * 421 | * @return Device 422 | */ 423 | public function getDevice(string $name): Device 424 | { 425 | $units = $this->listUnits($name, [Device::UNIT]); 426 | 427 | $unitName = $this->searchForUnitInUnits($name, $units); 428 | 429 | if (is_null($unitName)) { 430 | throw UnitNotFoundException::create(Device::UNIT, $name); 431 | } 432 | 433 | return new Device($unitName, $this->getCommandDispatcher()); 434 | } 435 | 436 | /** 437 | * @param string|null $unitPrefix 438 | * 439 | * @return Device[] 440 | */ 441 | public function getDevices(?string $unitPrefix = null): array 442 | { 443 | $units = $this->listUnits($unitPrefix, [Device::UNIT]); 444 | 445 | return array_map(function ($unitName) { 446 | return new Device($unitName, $this->getCommandDispatcher()); 447 | }, $units); 448 | } 449 | 450 | /** 451 | * @param string $name 452 | * 453 | * @return Automount 454 | */ 455 | public function getAutomount(string $name): Automount 456 | { 457 | $units = $this->listUnits($name, [Automount::UNIT]); 458 | 459 | $unitName = $this->searchForUnitInUnits($name, $units); 460 | 461 | if (is_null($unitName)) { 462 | throw UnitNotFoundException::create(Automount::UNIT, $name); 463 | } 464 | 465 | return new Automount($unitName, $this->getCommandDispatcher()); 466 | } 467 | 468 | /** 469 | * @param null|string $unitPrefix 470 | * 471 | * @return Automount[] 472 | */ 473 | public function getAutomounts(?string $unitPrefix = null): array 474 | { 475 | $units = $this->listUnits($unitPrefix, [Automount::UNIT]); 476 | 477 | return array_map(function ($unitName) { 478 | return new Automount($unitName, $this->getCommandDispatcher()); 479 | }, $units); 480 | } 481 | 482 | 483 | /** 484 | * @param string $name 485 | * 486 | * @return Mount 487 | */ 488 | public function getMount(string $name): Mount 489 | { 490 | $units = $this->listUnits($name, [Mount::UNIT]); 491 | 492 | $unitName = $this->searchForUnitInUnits($name, $units); 493 | 494 | if (is_null($unitName)) { 495 | throw UnitNotFoundException::create(Mount::UNIT, $name); 496 | } 497 | 498 | return new Mount($unitName, $this->getCommandDispatcher()); 499 | } 500 | 501 | /** 502 | * @param null|string $unitPrefix 503 | * 504 | * @return Mount[] 505 | */ 506 | public function getMounts(?string $unitPrefix = null): array 507 | { 508 | $units = $this->listUnits($unitPrefix, [Mount::UNIT]); 509 | 510 | return array_map(function ($unitName) { 511 | return new Mount($unitName, $this->getCommandDispatcher()); 512 | }, $units); 513 | } 514 | } 515 | -------------------------------------------------------------------------------- /src/Unit/AbstractUnit.php: -------------------------------------------------------------------------------- 1 | name = $name; 36 | $this->commandDispatcher = $commandDispatcher; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function getName(): string 43 | { 44 | return $this->name; 45 | } 46 | 47 | /** 48 | * @inheritDoc 49 | */ 50 | public function isMultiInstance(): bool 51 | { 52 | return strpos($this->name, '@') !== false; 53 | } 54 | 55 | /** 56 | * @inheritDoc 57 | * @todo: Everything in here should happen inside the constructor and stored 58 | * afterwards. 59 | */ 60 | public function getInstanceName(): ?string 61 | { 62 | $instanceName = explode('@', $this->name, 2)[1] ?? null; 63 | 64 | if (is_string($instanceName) && strpos($instanceName, '.') !== false) { 65 | $instanceName = explode('.', $instanceName, 2)[0]; 66 | } 67 | 68 | return $instanceName; 69 | } 70 | 71 | /** 72 | * @return string 73 | */ 74 | abstract protected function getUnitSuffix(): string; 75 | 76 | /** 77 | * @return CommandInterface 78 | * @throws CommandFailedException 79 | */ 80 | public function execute(string ...$commands): CommandInterface 81 | { 82 | $commands[] = implode( 83 | '.', 84 | [ 85 | $this->name, 86 | $this->getUnitSuffix(), 87 | ] 88 | ); 89 | 90 | return $this->commandDispatcher->dispatch(...$commands); 91 | } 92 | 93 | /** 94 | * @return bool 95 | */ 96 | public function start(): bool 97 | { 98 | return $this->execute(__FUNCTION__)->isSuccessful(); 99 | } 100 | 101 | /** 102 | * @return bool 103 | */ 104 | public function stop(): bool 105 | { 106 | return $this->execute(__FUNCTION__)->isSuccessful(); 107 | } 108 | 109 | /** 110 | * @return bool 111 | */ 112 | public function disable(): bool 113 | { 114 | return $this->execute(__FUNCTION__)->isSuccessful(); 115 | } 116 | 117 | /** 118 | * @return bool 119 | */ 120 | public function reload(): bool 121 | { 122 | return $this->execute(__FUNCTION__)->isSuccessful(); 123 | } 124 | 125 | /** 126 | * @return bool 127 | */ 128 | public function restart(): bool 129 | { 130 | return $this->execute(__FUNCTION__)->isSuccessful(); 131 | } 132 | 133 | /** 134 | * @return bool 135 | */ 136 | public function enable(): bool 137 | { 138 | return $this->execute(__FUNCTION__)->isSuccessful(); 139 | } 140 | 141 | /** 142 | * @return bool 143 | */ 144 | public function isEnabled(): bool 145 | { 146 | $output = $this->execute('is-enabled')->getOutput(); 147 | 148 | return trim($output) === 'enabled'; 149 | } 150 | 151 | /** 152 | * @return bool 153 | */ 154 | public function isActive(): bool 155 | { 156 | $output = $this->execute('is-active')->getOutput(); 157 | 158 | return trim($output) === 'active'; 159 | } 160 | 161 | /** 162 | * @return bool 163 | */ 164 | public function isRunning(): bool 165 | { 166 | return $this->isActive(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/Unit/Automount.php: -------------------------------------------------------------------------------- 1 | .*)\.' . $suffix . '\s.*$/m', $output, $matches); 23 | return $matches['unit'] ?? []; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Integration/Command/SymfonyCommandDispatcherTest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class SymfonyCommandDispatcherTest extends TestCase 15 | { 16 | /** 17 | * @test 18 | */ 19 | public function itShouldDispatchACorrectCommand(): void 20 | { 21 | $dispatcher = new SymfonyCommandDispatcher(); 22 | $dispatcher->setBinary('echo'); 23 | 24 | $output = $dispatcher->dispatch('a')->getOutput(); 25 | $this->assertSame('a', trim($output)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/Integration/SystemCtlTest.php: -------------------------------------------------------------------------------- 1 | prophesize(CommandDispatcherInterface::class); 31 | $commandDispatcher->setTimeout(Argument::any())->willReturn($commandDispatcher); 32 | $commandDispatcher->setBinary(Argument::any())->willReturn($commandDispatcher); 33 | 34 | return $commandDispatcher; 35 | } 36 | 37 | public function testListUnitsWithAvailableUnits(): void 38 | { 39 | $output = <<prophesize(CommandInterface::class); 55 | $command->getOutput()->willReturn($output); 56 | 57 | $dispatcherStub = $this->createCommandDispatcherStub(); 58 | $dispatcherStub->dispatch(Argument::cetera())->willReturn($command); 59 | 60 | $systemctl = new SystemCtl(); 61 | $systemctl->setCommandDispatcher($dispatcherStub->reveal()); 62 | 63 | $units = $systemctl->listUnits(null, SystemCtl::AVAILABLE_UNITS); 64 | self::assertCount(12, $units); 65 | } 66 | 67 | public function testListUnitsWithSupportedUnits(): void 68 | { 69 | $output = <<prophesize(CommandInterface::class); 84 | $command->getOutput()->willReturn($output); 85 | 86 | $dispatcherStub = $this->createCommandDispatcherStub(); 87 | $dispatcherStub->dispatch(Argument::cetera())->willReturn($command); 88 | 89 | $systemctl = new SystemCtl(); 90 | $systemctl->setCommandDispatcher($dispatcherStub->reveal()); 91 | 92 | $units = $systemctl->listUnits(); 93 | self::assertCount(5, $units); 94 | } 95 | 96 | public function testCreateUnitFromSupportedSuffixShouldWord(): void 97 | { 98 | $unit = SystemCtl::unitFromSuffix('service', 'SuccessService'); 99 | self::assertInstanceOf(UnitInterface::class, $unit); 100 | self::assertInstanceOf(Service::class, $unit); 101 | self::assertSame('SuccessService', $unit->getName()); 102 | } 103 | 104 | public function testCreateUnitFromUnsupportedSuffixShouldRaiseException(): void 105 | { 106 | $this->expectException(UnitTypeNotSupportedException::class); 107 | SystemCtl::unitFromSuffix('unsupported', 'FailUnit'); 108 | } 109 | 110 | public function testGetServices(): void 111 | { 112 | $output = <<prophesize(CommandInterface::class); 122 | $command->getOutput()->willReturn($output); 123 | 124 | $dispatcherStub = $this->createCommandDispatcherStub(); 125 | $dispatcherStub->dispatch(Argument::cetera())->willReturn($command); 126 | 127 | $systemctl = new SystemCtl(); 128 | $systemctl->setCommandDispatcher($dispatcherStub->reveal()); 129 | 130 | $services = $systemctl->getServices(); 131 | 132 | self::assertCount(2, $services); 133 | } 134 | 135 | public function testGetTimers(): void 136 | { 137 | $output = <<prophesize(CommandInterface::class); 147 | $command->getOutput()->willReturn($output); 148 | 149 | $dispatcherStub = $this->createCommandDispatcherStub(); 150 | $dispatcherStub->dispatch(Argument::cetera())->willReturn($command); 151 | 152 | $systemctl = new SystemCtl(); 153 | $systemctl->setCommandDispatcher($dispatcherStub->reveal()); 154 | $timers = $systemctl->getTimers(); 155 | 156 | self::assertCount(2, $timers); 157 | } 158 | 159 | /** 160 | * @test 161 | */ 162 | public function itShouldReturnTrueOnSuccessfulDaemonReload(): void 163 | { 164 | $command = $this->prophesize(CommandInterface::class); 165 | $command->isSuccessful()->willReturn(true); 166 | 167 | $dispatcher = $this->createCommandDispatcherStub(); 168 | $dispatcher->dispatch(Argument::exact('daemon-reload'))->willReturn($command); 169 | 170 | $systemCtl = new SystemCtl(); 171 | $systemCtl->setCommandDispatcher($dispatcher->reveal()); 172 | 173 | self::assertTrue($systemCtl->daemonReload()); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /test/Integration/Unit/UnitTest.php: -------------------------------------------------------------------------------- 1 | prophesize(CommandInterface::class); 35 | $command->isSuccessful()->willReturn(true); 36 | 37 | $commandDispatcher = $this->createCommandDispatcherStub(); 38 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 39 | 40 | $service = new Service('AwesomeService', $commandDispatcher->reveal()); 41 | 42 | self::assertTrue($service->start()); 43 | self::assertTrue($service->stop()); 44 | self::assertTrue($service->enable()); 45 | self::assertTrue($service->disable()); 46 | self::assertTrue($service->reload()); 47 | self::assertTrue($service->restart()); 48 | } 49 | 50 | public function createCommandDispatcherStub(): ObjectProphecy 51 | { 52 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 53 | $commandDispatcher->setTimeout(Argument::any())->willReturn($commandDispatcher); 54 | $commandDispatcher->setBinary(Argument::any())->willReturn($commandDispatcher); 55 | 56 | return $commandDispatcher; 57 | } 58 | 59 | public function testServiceCommandsIfProcessIsUnsuccessFulShouldRaiseException(): void 60 | { 61 | $commandDispatcher = $this->createCommandDispatcherStub(); 62 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 63 | 64 | $service = new Service('AwesomeService', $commandDispatcher->reveal()); 65 | 66 | $this->expectException(CommandFailedException::class); 67 | $service->start(); 68 | } 69 | 70 | public function testTimerCommandsIfProcessIsSuccessfulShouldReturnTrue(): void 71 | { 72 | $command = $this->prophesize(CommandInterface::class); 73 | $command->isSuccessful()->willReturn(true); 74 | 75 | $commandDispatcher = $this->createCommandDispatcherStub(); 76 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 77 | 78 | $timer = new Timer('AwesomeService', $commandDispatcher->reveal()); 79 | 80 | self::assertTrue($timer->start()); 81 | self::assertTrue($timer->stop()); 82 | self::assertTrue($timer->enable()); 83 | self::assertTrue($timer->disable()); 84 | self::assertTrue($timer->reload()); 85 | self::assertTrue($timer->restart()); 86 | } 87 | 88 | public function testTimerCommandsIfProcessIsUnsuccessFulShouldRaiseException(): void 89 | { 90 | $commandDispatcher = $this->createCommandDispatcherStub(); 91 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 92 | 93 | $timer = new Timer('AwesomeTimer', $commandDispatcher->reveal()); 94 | 95 | $this->expectException(CommandFailedException::class); 96 | $timer->start(); 97 | } 98 | 99 | public function testSocketCommandsIfProcessIsSuccessfulShouldReturnTrue(): void 100 | { 101 | $command = $this->prophesize(CommandInterface::class); 102 | $command->isSuccessful()->willReturn(true); 103 | 104 | $commandDispatcher = $this->createCommandDispatcherStub(); 105 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 106 | 107 | $socket = new Socket('AwesomeSocket', $commandDispatcher->reveal()); 108 | 109 | self::assertTrue($socket->start()); 110 | self::assertTrue($socket->stop()); 111 | self::assertTrue($socket->enable()); 112 | self::assertTrue($socket->disable()); 113 | self::assertTrue($socket->reload()); 114 | self::assertTrue($socket->restart()); 115 | } 116 | 117 | public function testSocketCommandsIfProcessIsUnsuccessFulShouldRaiseException(): void 118 | { 119 | $commandDispatcher = $this->createCommandDispatcherStub(); 120 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 121 | 122 | $socket = new Socket('AwesomeSocket', $commandDispatcher->reveal()); 123 | 124 | $this->expectException(CommandFailedException::class); 125 | $socket->start(); 126 | } 127 | 128 | public function testScopeCommandsIfProcessIsSuccessfulShouldReturnTrue(): void 129 | { 130 | $command = $this->prophesize(CommandInterface::class); 131 | $command->isSuccessful()->willReturn(true); 132 | 133 | $commandDispatcher = $this->createCommandDispatcherStub(); 134 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 135 | 136 | $scope = new Scope('AwesomeScope', $commandDispatcher->reveal()); 137 | 138 | self::assertTrue($scope->start()); 139 | self::assertTrue($scope->stop()); 140 | self::assertTrue($scope->enable()); 141 | self::assertTrue($scope->disable()); 142 | self::assertTrue($scope->reload()); 143 | self::assertTrue($scope->restart()); 144 | } 145 | 146 | public function testScopeCommandsIfProcessIsUnsuccessFulShouldRaiseException(): void 147 | { 148 | $commandDispatcher = $this->createCommandDispatcherStub(); 149 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 150 | 151 | $scope = new Scope('AwesomeScope', $commandDispatcher->reveal()); 152 | 153 | $this->expectException(CommandFailedException::class); 154 | $scope->start(); 155 | } 156 | 157 | public function testSliceCommandsIfProcessIsSuccessfulShouldReturnTrue() 158 | { 159 | $command = $this->prophesize(CommandInterface::class); 160 | $command->isSuccessful()->willReturn(true); 161 | 162 | $commandDispatcher = $this->createCommandDispatcherStub(); 163 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 164 | 165 | $slice = new Slice('AwesomeSlice', $commandDispatcher->reveal()); 166 | 167 | $this->assertTrue($slice->start()); 168 | $this->assertTrue($slice->stop()); 169 | $this->assertTrue($slice->enable()); 170 | $this->assertTrue($slice->disable()); 171 | $this->assertTrue($slice->reload()); 172 | $this->assertTrue($slice->restart()); 173 | } 174 | 175 | public function testSliceCommandsIfProcessIsUnsuccessFulShouldRaiseException() 176 | { 177 | $commandDispatcher = $this->createCommandDispatcherStub(); 178 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 179 | 180 | $slice = new Slice('AwesomeSlice', $commandDispatcher->reveal()); 181 | 182 | $this->expectException(CommandFailedException::class); 183 | $slice->start(); 184 | } 185 | 186 | public function testTargetCommandsIfProcessIsSuccessfulShouldReturnTrue() 187 | { 188 | $command = $this->prophesize(CommandInterface::class); 189 | $command->isSuccessful()->willReturn(true); 190 | 191 | $commandDispatcher = $this->createCommandDispatcherStub(); 192 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 193 | 194 | $target = new Target('AwesomeTarget', $commandDispatcher->reveal()); 195 | 196 | $this->assertTrue($target->start()); 197 | $this->assertTrue($target->stop()); 198 | $this->assertTrue($target->enable()); 199 | $this->assertTrue($target->disable()); 200 | $this->assertTrue($target->reload()); 201 | $this->assertTrue($target->restart()); 202 | } 203 | 204 | public function testTargetCommandsIfProcessIsUnsuccessFulShouldRaiseException() 205 | { 206 | $commandDispatcher = $this->createCommandDispatcherStub(); 207 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 208 | 209 | $target = new Target('AwesomeTarget', $commandDispatcher->reveal()); 210 | 211 | $this->expectException(CommandFailedException::class); 212 | $target->start(); 213 | } 214 | 215 | 216 | public function testSwapCommandsIfProcessIsSuccessfulShouldReturnTrue() 217 | { 218 | $command = $this->prophesize(CommandInterface::class); 219 | $command->isSuccessful()->willReturn(true); 220 | 221 | $commandDispatcher = $this->createCommandDispatcherStub(); 222 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 223 | 224 | $swap = new Swap('AwesomeSwap', $commandDispatcher->reveal()); 225 | 226 | $this->assertTrue($swap->start()); 227 | $this->assertTrue($swap->stop()); 228 | $this->assertTrue($swap->enable()); 229 | $this->assertTrue($swap->disable()); 230 | $this->assertTrue($swap->reload()); 231 | $this->assertTrue($swap->restart()); 232 | } 233 | 234 | public function testSwapCommandsIfProcessIsUnsuccessFulShouldRaiseException() 235 | { 236 | $commandDispatcher = $this->createCommandDispatcherStub(); 237 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 238 | 239 | $swap = new Swap('AwesomeSwap', $commandDispatcher->reveal()); 240 | 241 | $this->expectException(CommandFailedException::class); 242 | $swap->start(); 243 | } 244 | 245 | public function testDeviceCommandsIfProcessIsSuccessfulShouldReturnTrue() 246 | { 247 | $command = $this->prophesize(CommandInterface::class); 248 | $command->isSuccessful()->willReturn(true); 249 | 250 | $commandDispatcher = $this->createCommandDispatcherStub(); 251 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 252 | 253 | $device = new Device('AwesomeDevice', $commandDispatcher->reveal()); 254 | 255 | self::assertTrue($device->start()); 256 | self::assertTrue($device->stop()); 257 | self::assertTrue($device->enable()); 258 | self::assertTrue($device->disable()); 259 | self::assertTrue($device->reload()); 260 | self::assertTrue($device->restart()); 261 | } 262 | 263 | public function testDeviceCommandsIfProcessIsUnsuccessFulShouldRaiseException() 264 | { 265 | $commandDispatcher = $this->createCommandDispatcherStub(); 266 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 267 | 268 | $device = new Device('AwesomeDevice', $commandDispatcher->reveal()); 269 | 270 | $this->expectException(CommandFailedException::class); 271 | $device->start(); 272 | } 273 | 274 | public function testAutomountCommandsIfProcessIsSuccessfulShouldReturnTrue() 275 | { 276 | $command = $this->prophesize(CommandInterface::class); 277 | $command->isSuccessful()->willReturn(true); 278 | 279 | $commandDispatcher = $this->createCommandDispatcherStub(); 280 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 281 | 282 | $automount = new Automount('AwesomeAutomount', $commandDispatcher->reveal()); 283 | 284 | $this->assertTrue($automount->start()); 285 | $this->assertTrue($automount->stop()); 286 | $this->assertTrue($automount->enable()); 287 | $this->assertTrue($automount->disable()); 288 | $this->assertTrue($automount->reload()); 289 | $this->assertTrue($automount->restart()); 290 | } 291 | 292 | public function testAutomountCommandsIfProcessIsUnsuccessFulShouldRaiseException() 293 | { 294 | $commandDispatcher = $this->createCommandDispatcherStub(); 295 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 296 | 297 | $automount = new Automount('AwesomeAutomount', $commandDispatcher->reveal()); 298 | 299 | $this->expectException(CommandFailedException::class); 300 | $automount->start(); 301 | } 302 | 303 | public function testMountCommandsIfProcessIsSuccessfulShouldReturnTrue() 304 | { 305 | $command = $this->prophesize(CommandInterface::class); 306 | $command->isSuccessful()->willReturn(true); 307 | 308 | $commandDispatcher = $this->createCommandDispatcherStub(); 309 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 310 | 311 | $mount = new Mount('AwesomeMount', $commandDispatcher->reveal()); 312 | 313 | $this->assertTrue($mount->start()); 314 | $this->assertTrue($mount->stop()); 315 | $this->assertTrue($mount->enable()); 316 | $this->assertTrue($mount->disable()); 317 | $this->assertTrue($mount->reload()); 318 | $this->assertTrue($mount->restart()); 319 | } 320 | 321 | public function testMountCommandsIfProcessIsUnsuccessFulShouldRaiseException() 322 | { 323 | $commandDispatcher = $this->createCommandDispatcherStub(); 324 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 325 | 326 | $mount = new Mount('AwesomeMount', $commandDispatcher->reveal()); 327 | 328 | $this->expectException(CommandFailedException::class); 329 | $mount->start(); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /test/Unit/Command/SymfonyCommandTest.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class SymfonyCommandTest extends TestCase 18 | { 19 | use ProphecyTrait; 20 | 21 | /** 22 | * @test 23 | */ 24 | public function itShouldCreateValidInstance(): void 25 | { 26 | $process = $this->prophesize(Process::class); 27 | $command = new SymfonyCommand($process->reveal()); 28 | 29 | self::assertInstanceOf(SymfonyCommand::class, $command); 30 | } 31 | 32 | /** 33 | * @test 34 | */ 35 | public function itShouldReturnOutputFromProcess(): void 36 | { 37 | $process = $this->prophesize(Process::class); 38 | $process->getOutput()->willReturn('test'); 39 | 40 | $command = new SymfonyCommand($process->reveal()); 41 | self::assertSame('test', $command->getOutput()); 42 | } 43 | 44 | /** 45 | * @test 46 | */ 47 | public function itShouldReturnSuccessfulFromProcess(): void 48 | { 49 | $process = $this->prophesize(Process::class); 50 | $process->isSuccessful()->willReturn(true); 51 | 52 | $command = new SymfonyCommand($process->reveal()); 53 | self::assertTrue($command->isSuccessful()); 54 | } 55 | 56 | /** 57 | * @test 58 | */ 59 | public function itShouldReturnTheCommandIfCommandRanSuccessFul(): void 60 | { 61 | $process = $this->prophesize(Process::class); 62 | $process->run()->shouldBeCalled(); 63 | $process->getErrorOutput()->willReturn('testError'); 64 | $process->isSuccessful()->willReturn(true); 65 | 66 | $command = new SymfonyCommand($process->reveal()); 67 | self::assertSame($command, $command->run()); 68 | } 69 | 70 | /** 71 | * @test 72 | */ 73 | public function itShouldRaiseAnExceptionIfProcessWasNotSuccessfull(): void 74 | { 75 | $process = $this->prophesize(Process::class); 76 | $process->run()->shouldBeCalled(); 77 | $process->getErrorOutput()->willReturn('testError'); 78 | $process->isSuccessful()->willReturn(false); 79 | 80 | $command = new SymfonyCommand($process->reveal()); 81 | $this->expectException(CommandFailedException::class); 82 | $this->expectExceptionMessage('testError'); 83 | 84 | $command->run(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/Unit/SystemCtlTest.php: -------------------------------------------------------------------------------- 1 | prophesize(CommandDispatcherInterface::class); 39 | $commandDispatcherStub->setTimeout(Argument::type('int'))->willReturn($commandDispatcherStub); 40 | $commandDispatcherStub->setBinary(Argument::type('string'))->willReturn($commandDispatcherStub); 41 | 42 | return $commandDispatcherStub; 43 | } 44 | 45 | /** 46 | * @param string $output 47 | * 48 | * @return ObjectProphecy 49 | */ 50 | private function buildCommandStub(string $output): ObjectProphecy 51 | { 52 | $command = $this->prophesize(CommandInterface::class); 53 | $command->getOutput()->willReturn($output); 54 | 55 | return $command; 56 | } 57 | 58 | /** 59 | * @test 60 | */ 61 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnServiceGetting(): void 62 | { 63 | $unitName = 'testService'; 64 | $output = ' testService.service Active running'; 65 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 66 | $commandDispatcherStub 67 | ->dispatch(...['list-units', $unitName . '*']) 68 | ->willReturn($this->buildCommandStub($output)); 69 | 70 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 71 | 72 | $service = $systemctl->getService($unitName); 73 | self::assertSame('testService', $service->getName()); 74 | } 75 | 76 | /** 77 | * @test 78 | */ 79 | public function itShouldReturnAServiceOnServiceGetting(): void 80 | { 81 | $unitName = 'testService'; 82 | $output = ' testService.service Active running'; 83 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 84 | $commandDispatcherStub 85 | ->dispatch(...['list-units', $unitName . '*']) 86 | ->willReturn($this->buildCommandStub($output)); 87 | 88 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 89 | 90 | $service = $systemctl->getService($unitName); 91 | self::assertSame($unitName, $service->getName()); 92 | } 93 | 94 | /** 95 | * @test 96 | */ 97 | public function itShouldReturnAServiceWithTheCorrectNameOnServiceGetting(): void 98 | { 99 | $unitName = 'testService'; 100 | $output = ' testService.service Active running'; 101 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 102 | $commandDispatcherStub 103 | ->dispatch(...['list-units', $unitName . '*']) 104 | ->willReturn($this->buildCommandStub($output)); 105 | 106 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 107 | 108 | $service = $systemctl->getService($unitName); 109 | self::assertSame('testService', $service->getName()); 110 | } 111 | 112 | /** 113 | * @test 114 | */ 115 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnMountGetting() 116 | { 117 | $unitName = 'testMount'; 118 | $output = ' testMount.mount Active running'; 119 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 120 | $commandDispatcherStub 121 | ->dispatch(...['list-units', $unitName . '*']) 122 | ->willReturn($this->buildCommandStub($output)); 123 | 124 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 125 | 126 | $mount = $systemctl->getMount($unitName); 127 | $this->assertInstanceOf(Mount::class, $mount); 128 | $this->assertSame($unitName, $mount->getName()); 129 | } 130 | 131 | /** 132 | * @test 133 | */ 134 | public function itShouldThrowAnExceptionIfNotServicesCouldBeFound(): void 135 | { 136 | $unitName = 'testService'; 137 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 138 | $commandDispatcherStub 139 | ->dispatch(...['list-units', $unitName . '*']) 140 | ->willReturn($this->buildCommandStub('')); 141 | 142 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 143 | 144 | $this->expectException(UnitNotFoundException::class); 145 | $systemctl->getTimer($unitName); 146 | } 147 | 148 | /** 149 | * @test 150 | */ 151 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnTimerGetting(): void 152 | { 153 | $unitName = 'testTimer'; 154 | $output = ' testTimer.timer Active running'; 155 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 156 | $commandDispatcherStub 157 | ->dispatch(...['list-units', $unitName . '*']) 158 | ->willReturn($this->buildCommandStub($output)); 159 | 160 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 161 | 162 | $timer = $systemctl->getTimer($unitName); 163 | self::assertInstanceOf(Timer::class, $timer); 164 | self::assertSame($unitName, $timer->getName()); 165 | } 166 | 167 | /** 168 | * @test 169 | */ 170 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnSocketGetting(): void 171 | { 172 | $unitName = 'testSocket'; 173 | $output = ' testSocket.socket Active running'; 174 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 175 | $commandDispatcherStub 176 | ->dispatch(...['list-units', $unitName . '*']) 177 | ->willReturn($this->buildCommandStub($output)); 178 | 179 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 180 | 181 | $socket = $systemctl->getSocket($unitName); 182 | self::assertInstanceOf(Socket::class, $socket); 183 | self::assertSame($unitName, $socket->getName()); 184 | } 185 | 186 | /** 187 | * @test 188 | */ 189 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnScopeGetting(): void 190 | { 191 | $unitName = 'testScope'; 192 | $output = ' testScope.scope Active running'; 193 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 194 | $commandDispatcherStub 195 | ->dispatch(...['list-units', $unitName . '*']) 196 | ->willReturn($this->buildCommandStub($output)); 197 | 198 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 199 | 200 | $scope = $systemctl->getScope($unitName); 201 | self::assertInstanceOf(Scope::class, $scope); 202 | self::assertSame($unitName, $scope->getName()); 203 | } 204 | 205 | /** 206 | * @test 207 | */ 208 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnSliceGetting() 209 | { 210 | $unitName = 'testSlice'; 211 | $output = ' testSlice.slice Active running'; 212 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 213 | $commandDispatcherStub 214 | ->dispatch(...['list-units', $unitName . '*']) 215 | ->willReturn($this->buildCommandStub($output)); 216 | 217 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 218 | 219 | $slice = $systemctl->getSlice($unitName); 220 | $this->assertInstanceOf(Slice::class, $slice); 221 | $this->assertSame($unitName, $slice->getName()); 222 | } 223 | 224 | /** 225 | * @test 226 | */ 227 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnTargetGetting() 228 | { 229 | $unitName = 'testTarget'; 230 | $output = ' testTarget.target Active running'; 231 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 232 | $commandDispatcherStub 233 | ->dispatch(...['list-units', $unitName . '*']) 234 | ->willReturn($this->buildCommandStub($output)); 235 | 236 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 237 | 238 | $target = $systemctl->getTarget($unitName); 239 | $this->assertInstanceOf(Target::class, $target); 240 | $this->assertSame($unitName, $target->getName()); 241 | } 242 | 243 | /** 244 | * @test 245 | */ 246 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnSwapGetting() 247 | { 248 | $unitName = 'testSwap'; 249 | $output = ' testSwap.swap Active running'; 250 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 251 | $commandDispatcherStub 252 | ->dispatch(...['list-units', $unitName . '*']) 253 | ->willReturn($this->buildCommandStub($output)); 254 | 255 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 256 | 257 | $swap = $systemctl->getSwap($unitName); 258 | $this->assertInstanceOf(Swap::class, $swap); 259 | $this->assertSame($unitName, $swap->getName()); 260 | } 261 | 262 | /** 263 | * @test 264 | */ 265 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnAutomountGetting() 266 | { 267 | $unitName = 'testAutomount'; 268 | $output = ' testAutomount.automount Active running'; 269 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 270 | $commandDispatcherStub 271 | ->dispatch(...['list-units', $unitName . '*']) 272 | ->willReturn($this->buildCommandStub($output)); 273 | 274 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 275 | 276 | $automount = $systemctl->getAutomount($unitName); 277 | $this->assertInstanceOf(Automount::class, $automount); 278 | $this->assertSame($unitName, $automount->getName()); 279 | } 280 | 281 | /** 282 | * @test 283 | */ 284 | public function itShouldThrowAnExeceptionIfNotTimerCouldBeFound(): void 285 | { 286 | $unitName = 'testTimer'; 287 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 288 | $commandDispatcherStub 289 | ->dispatch(...['list-units', $unitName . '*']) 290 | ->willReturn($this->buildCommandStub('')); 291 | 292 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 293 | 294 | $this->expectException(UnitNotFoundException::class); 295 | $systemctl->getTimer($unitName); 296 | } 297 | 298 | /** 299 | * @test 300 | */ 301 | public function itShouldThrowAnExceptionIfNoSocketCouldBeFound(): void 302 | { 303 | $unitName = 'testSocket'; 304 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 305 | $commandDispatcherStub 306 | ->dispatch(...['list-units', $unitName . '*']) 307 | ->willReturn($this->buildCommandStub('')); 308 | 309 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 310 | 311 | $this->expectException(UnitNotFoundException::class); 312 | $systemctl->getSocket($unitName); 313 | } 314 | 315 | /** 316 | * @test 317 | */ 318 | public function itShouldThrowAnExceptionIfNoTargetCouldBeFound() 319 | { 320 | $unitName = 'testTarget'; 321 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 322 | $commandDispatcherStub 323 | ->dispatch(...['list-units', $unitName . '*']) 324 | ->willReturn($this->buildCommandStub('')); 325 | 326 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 327 | 328 | $this->expectException(UnitNotFoundException::class); 329 | $systemctl->getTarget($unitName); 330 | } 331 | 332 | /** 333 | * @test 334 | */ 335 | public function itShouldThrowAnExceptionIfNoSwapCouldBeFound() 336 | { 337 | $unitName = 'testSwap'; 338 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 339 | $commandDispatcherStub 340 | ->dispatch(...['list-units', $unitName . '*']) 341 | ->willReturn($this->buildCommandStub('')); 342 | 343 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 344 | 345 | $this->expectException(UnitNotFoundException::class); 346 | $systemctl->getSwap($unitName); 347 | } 348 | 349 | /** 350 | * @test 351 | */ 352 | public function itShouldThrowAnExceptionIfNoScopeCouldBeFound(): void 353 | { 354 | $unitName = 'testScope'; 355 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 356 | $commandDispatcherStub 357 | ->dispatch(...['list-units', $unitName . '*']) 358 | ->willReturn($this->buildCommandStub('')); 359 | 360 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 361 | 362 | $this->expectException(UnitNotFoundException::class); 363 | $systemctl->getScope($unitName); 364 | } 365 | 366 | /** 367 | * @test 368 | */ 369 | public function itShouldThrowAnExceptionIfNoSliceCouldBeFound() 370 | { 371 | $unitName = 'testSlice'; 372 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 373 | $commandDispatcherStub 374 | ->dispatch(...['list-units', $unitName . '*']) 375 | ->willReturn($this->buildCommandStub('')); 376 | 377 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 378 | 379 | $this->expectException(UnitNotFoundException::class); 380 | $systemctl->getSlice($unitName); 381 | } 382 | 383 | /** 384 | * @test 385 | */ 386 | public function itShouldReturnATimerOnTimerGetting(): void 387 | { 388 | $unitName = 'testService'; 389 | $output = ' testService.service Active running'; 390 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 391 | $commandDispatcherStub 392 | ->dispatch(...['list-units', $unitName . '*']) 393 | ->willReturn($this->buildCommandStub($output)); 394 | 395 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 396 | 397 | $service = $systemctl->getService($unitName); 398 | self::assertInstanceOf(Service::class, $service); 399 | } 400 | 401 | /** 402 | * @test 403 | */ 404 | public function itShouldReturnATimerWithTheCorrectNameOnTimerGetting(): void 405 | { 406 | $unitName = 'testService'; 407 | $output = ' testService.service Active running'; 408 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 409 | $commandDispatcherStub 410 | ->dispatch(...['list-units', $unitName . '*']) 411 | ->willReturn($this->buildCommandStub($output)); 412 | 413 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 414 | 415 | $service = $systemctl->getService($unitName); 416 | self::assertSame('testService', $service->getName()); 417 | } 418 | 419 | /** 420 | * @test 421 | */ 422 | public function itShouldCallCommandDispatcherWithListUnitsAndUnitPrefixOnDeviceGetting() 423 | { 424 | $unitName = 'testDevice'; 425 | $output = ' testDevice.device Active running'; 426 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 427 | $commandDispatcherStub 428 | ->dispatch(...['list-units', $unitName . '*']) 429 | ->willReturn($this->buildCommandStub($output)); 430 | 431 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 432 | 433 | $device = $systemctl->getDevice($unitName); 434 | self::assertInstanceOf(Device::class, $device); 435 | self::assertSame($unitName, $device->getName()); 436 | } 437 | 438 | /** 439 | * @test 440 | */ 441 | public function itShouldThrowAnExceptionIfNoDeviceCouldBeFound() 442 | { 443 | $unitName = 'testDevice'; 444 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 445 | $commandDispatcherStub 446 | ->dispatch(...['list-units', $unitName . '*']) 447 | ->willReturn($this->buildCommandStub('')); 448 | 449 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 450 | 451 | $this->expectException(UnitNotFoundException::class); 452 | $systemctl->getSocket($unitName); 453 | } 454 | 455 | /** 456 | * @test 457 | */ 458 | public function itShouldThrowAnExceptionIfNoAutomountCouldBeFound() 459 | { 460 | $unitName = 'testAutomount'; 461 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 462 | $commandDispatcherStub 463 | ->dispatch(...['list-units', $unitName . '*']) 464 | ->willReturn($this->buildCommandStub('')); 465 | 466 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 467 | 468 | $this->expectException(UnitNotFoundException::class); 469 | $systemctl->getAutomount($unitName); 470 | } 471 | 472 | /** 473 | * @test 474 | */ 475 | public function itShouldThrowAnExceptionIfNoMountCouldBeFound() 476 | { 477 | $unitName = 'testMount'; 478 | $commandDispatcherStub = $this->buildCommandDispatcherStub(); 479 | $commandDispatcherStub 480 | ->dispatch(...['list-units', $unitName . '*']) 481 | ->willReturn($this->buildCommandStub('')); 482 | 483 | $systemctl = (new SystemCtl())->setCommandDispatcher($commandDispatcherStub->reveal()); 484 | 485 | $this->expectException(UnitNotFoundException::class); 486 | $systemctl->getMount($unitName); 487 | } 488 | } 489 | -------------------------------------------------------------------------------- /test/Unit/Unit/AbstractUnitTest.php: -------------------------------------------------------------------------------- 1 | prophesize(CommandDispatcherInterface::class); 35 | $unit = new UnitStub($name, $commandDispatcher->reveal()); 36 | 37 | self::assertSame($name, $unit->getName()); 38 | } 39 | 40 | /** 41 | * @return array 42 | */ 43 | public function itShouldReturnCorrectNameDataProvider(): array 44 | { 45 | return [ 46 | [ 47 | 'name' => 'test1', 48 | ], 49 | [ 50 | 'name' => 'test1.service', 51 | ], 52 | [ 53 | 'name' => 'test1.timer', 54 | ], 55 | [ 56 | 'name' => 'test1.socket', 57 | ], 58 | [ 59 | 'name' => 'test1.scope', 60 | ], 61 | [ 62 | 'name' => 'test1.slice', 63 | ], 64 | [ 65 | 'name' => 'test1.swap', 66 | ], 67 | [ 68 | 'name' => 'test1.target', 69 | ], 70 | [ 71 | 'name' => 'test1.mount', 72 | ], 73 | [ 74 | 'name' => 'test1.automount', 75 | ], 76 | [ 77 | 'name' => 'test1.mount', 78 | ], 79 | [ 80 | 'name' => 'test1.device', 81 | ], 82 | [ 83 | 'name' => 'test1@2.service', 84 | ], 85 | ]; 86 | } 87 | 88 | /** 89 | * @param string $name 90 | * @param bool $isMultiInstance 91 | * 92 | * @test 93 | * @dataProvider itDetectsMultiInstanceUnitsCorrectlyDataProvider 94 | */ 95 | public function itDetectsMultiInstanceUnitsCorrectly(string $name, bool $isMultiInstance): void 96 | { 97 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 98 | $unit = new UnitStub($name, $commandDispatcher->reveal()); 99 | 100 | self::assertSame($isMultiInstance, $unit->isMultiInstance()); 101 | } 102 | 103 | /** 104 | * @return array 105 | */ 106 | public function itDetectsMultiInstanceUnitsCorrectlyDataProvider(): array 107 | { 108 | return [ 109 | [ 110 | 'name' => 'test1@1', 111 | 'isMultiInstance' => true, 112 | ], 113 | [ 114 | 'name' => 'test1@123.service', 115 | 'isMultiInstance' => true, 116 | ], 117 | [ 118 | 'name' => 'test1@foo', 119 | 'isMultiInstance' => true, 120 | ], 121 | [ 122 | 'name' => 'test1@foo.mount', 123 | 'isMultiInstance' => true, 124 | ], 125 | [ 126 | 'name' => 'test1@foo.automount', 127 | 'isMultiInstance' => true, 128 | ], 129 | [ 130 | 'name' => 'test1.service', 131 | 'isMultiInstance' => false, 132 | ], 133 | [ 134 | 'name' => 'test1.timer', 135 | 'isMultiInstance' => false, 136 | ], 137 | [ 138 | 'name' => 'test1.socket', 139 | 'isMultiInstance' => false, 140 | ], 141 | [ 142 | 'name' => 'test1.device', 143 | 'isMultiInstance' => false, 144 | ], 145 | [ 146 | 'name' => 'test1.scope', 147 | 'isMultiInstance' => false, 148 | ], 149 | [ 150 | 'name' => 'test1.slice', 151 | 'isMultiInstance' => false, 152 | ], 153 | [ 154 | 'name' => 'test1.swap', 155 | 'isMultiInstance' => false, 156 | ], 157 | [ 158 | 'name' => 'test1.target', 159 | 'isMultiInstance' => false, 160 | ], 161 | [ 162 | 'name' => 'test1.mount', 163 | 'isMultiInstance' => false, 164 | ], 165 | ]; 166 | } 167 | 168 | /** 169 | * @param string $name 170 | * @param string $instanceName 171 | * 172 | * @test 173 | * @dataProvider itDetectsMultiInstanceInstanceNamesCorrectlyDataProvider 174 | */ 175 | public function itDetectsMultiInstanceInstanceNamesCorrectly(string $name, ?string $instanceName): void 176 | { 177 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 178 | $unit = new UnitStub($name, $commandDispatcher->reveal()); 179 | 180 | self::assertSame($instanceName, $unit->getInstanceName()); 181 | } 182 | 183 | /** 184 | * @return array 185 | */ 186 | public function itDetectsMultiInstanceInstanceNamesCorrectlyDataProvider(): array 187 | { 188 | return [ 189 | [ 190 | 'name' => 'test1@1', 191 | 'instanceName' => '1', 192 | ], 193 | [ 194 | 'name' => 'test1@123.service', 195 | 'instanceName' => '123', 196 | ], 197 | [ 198 | 'name' => 'test1@foo', 199 | 'instanceName' => 'foo', 200 | ], 201 | [ 202 | 'name' => 'test1@foo.mount', 203 | 'instanceName' => 'foo', 204 | ], 205 | [ 206 | 'name' => 'test1.service', 207 | 'instanceName' => null, 208 | ], 209 | [ 210 | 'name' => 'test1.timer', 211 | 'instanceName' => null, 212 | ], 213 | [ 214 | 'name' => 'test1.socket', 215 | 'instanceName' => null, 216 | ], 217 | [ 218 | 'name' => 'test1.scope', 219 | 'instanceName' => null, 220 | ], 221 | [ 222 | 'name' => 'test1.slice', 223 | 'instanceName' => null, 224 | ], 225 | [ 226 | 'name' => 'test1.swap', 227 | 'instanceName' => null, 228 | ], 229 | [ 230 | 'name' => 'test1.target', 231 | 'instanceName' => null, 232 | ], 233 | [ 234 | 'name' => 'test1.mount', 235 | 'instanceName' => null, 236 | ], 237 | ]; 238 | } 239 | 240 | /** 241 | * @test 242 | */ 243 | public function itShouldReturnTrueIfServiceEnabledCommandRanSuccessfully(): void 244 | { 245 | $command = $this->prophesize(CommandInterface::class); 246 | $command->getOutput()->willReturn('enabled'); 247 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 248 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 249 | 250 | $unit = new UnitStub(static::UNIT_NAME, $commandDispatcher->reveal()); 251 | 252 | self::assertTrue($unit->isEnabled()); 253 | } 254 | 255 | /** 256 | * @test 257 | */ 258 | public function itShouldRaiseAnExceptionIfServiceEnabledCommandFailed(): void 259 | { 260 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 261 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 262 | 263 | $unit = new UnitStub(static::UNIT_NAME, $commandDispatcher->reveal()); 264 | $this->expectException(CommandFailedException::class); 265 | 266 | $unit->isEnabled(); 267 | } 268 | 269 | /** 270 | * @param bool $commandSuccessful 271 | * @param string $commandOutput 272 | * 273 | * @test 274 | * @dataProvider itShouldReturnFalseIfServiceEnabledCommandOutputDoesNotEqualEnabledDataProvider 275 | */ 276 | public function itShouldReturnFalseIfServiceEnabledCommandOutputDoesNotEqualEnabled( 277 | $commandSuccessful, 278 | $commandOutput 279 | ): void { 280 | $command = $this->prophesize(CommandInterface::class); 281 | $command->isSuccessful()->willReturn($commandSuccessful); 282 | $command->getOutput()->willReturn($commandOutput); 283 | 284 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 285 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 286 | 287 | $unit = new UnitStub(static::UNIT_NAME, $commandDispatcher->reveal()); 288 | 289 | self::assertFalse($unit->isEnabled()); 290 | } 291 | 292 | /** 293 | * @return array 294 | */ 295 | public function itShouldReturnFalseIfServiceEnabledCommandOutputDoesNotEqualEnabledDataProvider(): array 296 | { 297 | return [ 298 | [ 299 | 'commandSuccessful' => true, 300 | 'commandOutput' => 'static', 301 | ], 302 | [ 303 | 'commandSuccessful' => false, 304 | 'commandOutput' => 'static', 305 | ], 306 | [ 307 | 'commandSuccessful' => true, 308 | 'commandOutput' => 'enable', 309 | ], 310 | ]; 311 | } 312 | 313 | /** 314 | * @test 315 | */ 316 | public function itShouldReturnTrueIfServiceActiveCommandRanSuccessfully(): void 317 | { 318 | $command = $this->prophesize(CommandInterface::class); 319 | $command->getOutput()->willReturn('active'); 320 | 321 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 322 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 323 | 324 | $unit = new UnitStub(static::UNIT_NAME, $commandDispatcher->reveal()); 325 | 326 | self::assertTrue($unit->isRunning()); 327 | } 328 | 329 | /** 330 | * @test 331 | */ 332 | public function itShouldRaiseExceptionIfServiceActiveCommandFailed(): void 333 | { 334 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 335 | $commandDispatcher->dispatch(Argument::cetera())->willThrow(CommandFailedException::class); 336 | 337 | $unit = new UnitStub(static::UNIT_NAME, $commandDispatcher->reveal()); 338 | 339 | $this->expectException(CommandFailedException::class); 340 | $unit->isRunning(); 341 | } 342 | 343 | /** 344 | * @param bool $commandSuccessful 345 | * @param string $commandOutput 346 | * 347 | * @test 348 | * @dataProvider itShouldReturnFalseIfServiceActiveCommandOutputDoesNotEqualActiveDataProvider 349 | */ 350 | public function itShouldReturnFalseIfServiceActiveCommandOutputDoesNotEqualActive( 351 | $commandSuccessful, 352 | $commandOutput 353 | ): void { 354 | $command = $this->prophesize(CommandInterface::class); 355 | $command->isSuccessful()->willReturn($commandSuccessful); 356 | $command->getOutput()->willReturn($commandOutput); 357 | 358 | $commandDispatcher = $this->prophesize(CommandDispatcherInterface::class); 359 | $commandDispatcher->dispatch(Argument::cetera())->willReturn($command); 360 | 361 | $unit = new UnitStub(static::UNIT_NAME, $commandDispatcher->reveal()); 362 | 363 | self::assertFalse($unit->isRunning()); 364 | } 365 | 366 | /** 367 | * @return array 368 | */ 369 | public function itShouldReturnFalseIfServiceActiveCommandOutputDoesNotEqualActiveDataProvider(): array 370 | { 371 | return [ 372 | [ 373 | 'commandSuccessful' => true, 374 | 'commandOutput' => 'static', 375 | ], 376 | [ 377 | 'commandSuccessful' => false, 378 | 'commandOutput' => 'static', 379 | ], 380 | [ 381 | 'commandSuccessful' => true, 382 | 'commandOutput' => 'enable', 383 | ], 384 | ]; 385 | } 386 | 387 | /** 388 | * @test 389 | */ 390 | public function testIfExecuteAppendsTheUnitNameAndSuffix(): void 391 | { 392 | $commandStub = $this->prophesize(CommandInterface::class); 393 | $commandStub->isSuccessful()->willReturn(true); 394 | 395 | $commandDispatcherStub = $this->prophesize(CommandDispatcherInterface::class); 396 | $commandDispatcherStub 397 | ->dispatch(...['start', self::UNIT_NAME . '.' . 'stub']) 398 | ->willReturn($commandStub) 399 | ->shouldBeCalled(); 400 | 401 | $unitStub = new UnitStub(self::UNIT_NAME, $commandDispatcherStub->reveal()); 402 | $unitStub->start(); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /test/Unit/Unit/UnitStub.php: -------------------------------------------------------------------------------- 1 | $output, 71 | 'suffix' => 'service', 72 | 'amount' => 5, 73 | ], 74 | [ 75 | 'output' => $output, 76 | 'suffix' => 'timer', 77 | 'amount' => 2, 78 | ], 79 | [ 80 | 'output' => $output, 81 | 'suffix' => 'socket', 82 | 'amount' => 2, 83 | ], 84 | [ 85 | 'output' => $output, 86 | 'suffix' => 'device', 87 | 'amount' => 2, 88 | ], 89 | [ 90 | 'output' => $output, 91 | 'suffix' => 'scope', 92 | 'amount' => 2, 93 | ], 94 | [ 95 | 'output' => $output, 96 | 'suffix' => 'slice', 97 | 'amount' => 2, 98 | ], 99 | [ 100 | 'output' => $output, 101 | 'suffix' => 'swap', 102 | 'amount' => 2, 103 | ], 104 | [ 105 | 'output' => $output, 106 | 'suffix' => 'target', 107 | 'amount' => 2, 108 | ], 109 | [ 110 | 'output' => $output, 111 | 'suffix' => 'mount', 112 | 'amount' => 4, 113 | ], 114 | [ 115 | 'output' => $output, 116 | 'suffix' => 'automount', 117 | 'amount' => 2, 118 | ], 119 | [ 120 | 'output' => $output, 121 | 'suffix' => 'notThere', 122 | 'amount' => 0, 123 | ], 124 | ]; 125 | } 126 | 127 | /** 128 | * @param string $output 129 | * @param string $suffix 130 | * @param array $expectedUnitNames 131 | * 132 | * @test 133 | * @dataProvider itOnlyExtractsTheUnitNamesDataProvider 134 | */ 135 | public function itOnlyExtractsTheUnitNames(string $output, string $suffix, array $expectedUnitNames): void 136 | { 137 | $units = OutputFetcher::fetchUnitNames($suffix, $output); 138 | self::assertSame($expectedUnitNames, $units); 139 | } 140 | 141 | /** 142 | * @return array 143 | */ 144 | public function itOnlyExtractsTheUnitNamesDataProvider(): array 145 | { 146 | $output = << $output, 177 | 'suffix' => 'service', 178 | 'units' => [ 179 | 'foo', 180 | 'foo-bar', 181 | 'instance-service@1', 182 | 'instance-service@foo', 183 | 'failed-service@foo' 184 | ], 185 | ], 186 | [ 187 | 'output' => $output, 188 | 'suffix' => 'timer', 189 | 'units' => [ 190 | 'a-timer', 191 | 'nonservice', 192 | ], 193 | ], 194 | [ 195 | 'output' => $output, 196 | 'suffix' => 'device', 197 | 'units' => [ 198 | 'a-device', 199 | 'nonservice', 200 | ], 201 | ], 202 | [ 203 | 'output' => $output, 204 | 'suffix' => 'socket', 205 | 'units' => [ 206 | 'a-socket', 207 | 'nonservice', 208 | ], 209 | ], 210 | [ 211 | 'output' => $output, 212 | 'suffix' => 'scope', 213 | 'units' => [ 214 | 'a-scope', 215 | 'nonservice', 216 | ], 217 | ], 218 | [ 219 | 'output' => $output, 220 | 'suffix' => 'slice', 221 | 'units' => [ 222 | 'a-slice', 223 | 'nonservice', 224 | ], 225 | ], 226 | [ 227 | 'output' => $output, 228 | 'suffix' => 'swap', 229 | 'units' => [ 230 | 'a-swap', 231 | 'nonservice', 232 | ], 233 | ], 234 | [ 235 | 'output' => $output, 236 | 'suffix' => 'target', 237 | 'units' => [ 238 | 'a-target', 239 | 'nonservice', 240 | ], 241 | ], 242 | [ 243 | 'output' => $output, 244 | 'suffix' => 'mount', 245 | 'units' => [ 246 | 'a-mount', 247 | 'super', 248 | 'awesome', 249 | 'nonservice', 250 | ], 251 | ], 252 | [ 253 | 'output' => $output, 254 | 'suffix' => 'automount', 255 | 'units' => [ 256 | 'super', 257 | 'awesome', 258 | ], 259 | ], 260 | ]; 261 | } 262 | } 263 | --------------------------------------------------------------------------------