-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7978 from kenjis/feat-cli-io
feat: improve CLI input testability
- Loading branch information
Showing
9 changed files
with
518 additions
and
121 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* This file is part of CodeIgniter 4 framework. | ||
* | ||
* (c) CodeIgniter Foundation <[email protected]> | ||
* | ||
* For the full copyright and license information, please view | ||
* the LICENSE file that was distributed with this source code. | ||
*/ | ||
|
||
namespace CodeIgniter\CLI; | ||
|
||
/** | ||
* Input and Output for CLI. | ||
*/ | ||
class InputOutput | ||
{ | ||
/** | ||
* Is the readline library on the system? | ||
*/ | ||
private bool $readlineSupport; | ||
|
||
public function __construct() | ||
{ | ||
// Readline is an extension for PHP that makes interactivity with PHP | ||
// much more bash-like. | ||
// http://www.php.net/manual/en/readline.installation.php | ||
$this->readlineSupport = extension_loaded('readline'); | ||
} | ||
|
||
/** | ||
* Get input from the shell, using readline or the standard STDIN | ||
* | ||
* Named options must be in the following formats: | ||
* php index.php user -v --v -name=John --name=John | ||
* | ||
* @param string|null $prefix You may specify a string with which to prompt the user. | ||
*/ | ||
public function input(?string $prefix = null): string | ||
{ | ||
// readline() can't be tested. | ||
if ($this->readlineSupport && ENVIRONMENT !== 'testing') { | ||
return readline($prefix); // @codeCoverageIgnore | ||
} | ||
|
||
echo $prefix; | ||
|
||
$input = fgets(fopen('php://stdin', 'rb')); | ||
|
||
if ($input === false) { | ||
$input = ''; | ||
} | ||
|
||
return $input; | ||
} | ||
|
||
/** | ||
* While the library is intended for use on CLI commands, | ||
* commands can be called from controllers and elsewhere | ||
* so we need a way to allow them to still work. | ||
* | ||
* For now, just echo the content, but look into a better | ||
* solution down the road. | ||
* | ||
* @param resource $handle | ||
*/ | ||
public function fwrite($handle, string $string): void | ||
{ | ||
if (! is_cli()) { | ||
echo $string; | ||
|
||
return; | ||
} | ||
|
||
fwrite($handle, $string); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
/** | ||
* This file is part of CodeIgniter 4 framework. | ||
* | ||
* (c) CodeIgniter Foundation <[email protected]> | ||
* | ||
* For the full copyright and license information, please view | ||
* the LICENSE file that was distributed with this source code. | ||
*/ | ||
|
||
namespace CodeIgniter\Test\Mock; | ||
|
||
use CodeIgniter\CLI\InputOutput; | ||
use CodeIgniter\Test\Filters\CITestStreamFilter; | ||
use CodeIgniter\Test\PhpStreamWrapper; | ||
use InvalidArgumentException; | ||
use LogicException; | ||
|
||
final class MockInputOutput extends InputOutput | ||
{ | ||
/** | ||
* String to be entered by the user. | ||
* | ||
* @var list<string> | ||
*/ | ||
private array $inputs = []; | ||
|
||
/** | ||
* Output lines. | ||
* | ||
* @var array<int, string> | ||
* @phpstan-var list<string> | ||
*/ | ||
private array $outputs = []; | ||
|
||
/** | ||
* Sets user inputs. | ||
* | ||
* @param array<int, string> $inputs | ||
* @phpstan-param list<string> $inputs | ||
*/ | ||
public function setInputs(array $inputs): void | ||
{ | ||
$this->inputs = $inputs; | ||
} | ||
|
||
/** | ||
* Gets the item from the output array. | ||
* | ||
* @param int|null $index The output array index. If null, returns all output | ||
* string. If negative int, returns the last $index-th | ||
* item. | ||
*/ | ||
public function getOutput(?int $index = null): string | ||
{ | ||
if ($index === null) { | ||
return implode('', $this->outputs); | ||
} | ||
|
||
if (array_key_exists($index, $this->outputs)) { | ||
return $this->outputs[$index]; | ||
} | ||
|
||
if ($index < 0) { | ||
$i = count($this->outputs) + $index; | ||
|
||
if (array_key_exists($i, $this->outputs)) { | ||
return $this->outputs[$i]; | ||
} | ||
} | ||
|
||
throw new InvalidArgumentException( | ||
'No such index in output: ' . $index . ', the last index is: ' | ||
. (count($this->outputs) - 1) | ||
); | ||
} | ||
|
||
/** | ||
* Returns the outputs array. | ||
*/ | ||
public function getOutputs(): array | ||
{ | ||
return $this->outputs; | ||
} | ||
|
||
private function addStreamFilters(): void | ||
{ | ||
CITestStreamFilter::registration(); | ||
CITestStreamFilter::addOutputFilter(); | ||
CITestStreamFilter::addErrorFilter(); | ||
} | ||
|
||
private function removeStreamFilters(): void | ||
{ | ||
CITestStreamFilter::removeOutputFilter(); | ||
CITestStreamFilter::removeErrorFilter(); | ||
} | ||
|
||
public function input(?string $prefix = null): string | ||
{ | ||
if ($this->inputs === []) { | ||
throw new LogicException( | ||
'No input data. Specifiy input data with `MockInputOutput::setInputs()`.' | ||
); | ||
} | ||
|
||
$input = array_shift($this->inputs); | ||
|
||
$this->addStreamFilters(); | ||
|
||
PhpStreamWrapper::register(); | ||
PhpStreamWrapper::setContent($input); | ||
|
||
$userInput = parent::input($prefix); | ||
$this->outputs[] = CITestStreamFilter::$buffer . $input . PHP_EOL; | ||
|
||
PhpStreamWrapper::restore(); | ||
|
||
$this->removeStreamFilters(); | ||
|
||
if ($input !== $userInput) { | ||
throw new LogicException($input . '!==' . $userInput); | ||
} | ||
|
||
return $input; | ||
} | ||
|
||
public function fwrite($handle, string $string): void | ||
{ | ||
$this->addStreamFilters(); | ||
|
||
parent::fwrite($handle, $string); | ||
$this->outputs[] = CITestStreamFilter::$buffer; | ||
|
||
$this->removeStreamFilters(); | ||
} | ||
} |
Oops, something went wrong.