-
Notifications
You must be signed in to change notification settings - Fork 1.9k
feat: improve CLI input testability #7978
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
6f5bb00
feat: add InputOutput and MockInputOutput class
kenjis a6fe7f7
refactor: use InputOutput in CLI class
kenjis 5173b38
test: add test for user input in CLI
kenjis 099e816
docs: remove outdated comment
kenjis d5fc5e7
fix: MockInputOutput output
kenjis 85ad0c9
test: fix assertion
kenjis 1e78628
docs: move Testing CLI to a new page
kenjis 53c76b6
docs: add docs
kenjis 8014b6b
test: update assertion
kenjis 9ff8db2
docs: fix by proofreading
kenjis 086ca7f
refactor: add final to MockInputOutput
kenjis 835462d
refactor: add strict_types
kenjis 233f7e4
docs: native @var tag
kenjis 962777e
docs: fix by proofreading
kenjis 2ef3169
docs: add @testTag
kenjis 6767da7
refactor: add declare(strict_types=1) to new class
kenjis 0321cc0
fix: TypeError in InputOutput::input()
kenjis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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; | ||
MGatner marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* 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 hidden or 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 | ||
|
||
kenjis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.