1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org) |
4
|
|
|
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) |
5
|
|
|
* |
6
|
|
|
* Licensed under The MIT License |
7
|
|
|
* For full copyright and license information, please see the LICENSE.txt |
8
|
|
|
* Redistributions of files must retain the above copyright notice |
9
|
|
|
* |
10
|
|
|
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org) |
11
|
|
|
* @since 3.7.0 |
12
|
|
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
13
|
|
|
*/ |
14
|
|
|
namespace Cake\TestSuite; |
15
|
|
|
|
16
|
|
|
use Cake\Console\CommandRunner; |
17
|
|
|
use Cake\Console\ConsoleInput; |
18
|
|
|
use Cake\Console\ConsoleIo; |
19
|
|
|
use Cake\Console\Exception\StopException; |
20
|
|
|
use Cake\Core\Configure; |
21
|
|
|
use Cake\TestSuite\Constraint\Console\ContentsContain; |
22
|
|
|
use Cake\TestSuite\Constraint\Console\ContentsContainRow; |
23
|
|
|
use Cake\TestSuite\Constraint\Console\ContentsEmpty; |
24
|
|
|
use Cake\TestSuite\Constraint\Console\ContentsNotContain; |
25
|
|
|
use Cake\TestSuite\Constraint\Console\ContentsRegExp; |
26
|
|
|
use Cake\TestSuite\Constraint\Console\ExitCode; |
27
|
|
|
use Cake\TestSuite\Stub\ConsoleOutput; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* A test case class intended to make integration tests of cake console commands |
31
|
|
|
* easier. |
32
|
|
|
*/ |
33
|
|
|
trait ConsoleIntegrationTestTrait |
34
|
|
|
{ |
35
|
|
|
/** |
36
|
|
|
* Whether or not to use the CommandRunner |
37
|
|
|
* |
38
|
|
|
* @var bool |
39
|
|
|
*/ |
40
|
|
|
protected $_useCommandRunner = false; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Last exit code |
44
|
|
|
* |
45
|
|
|
* @var int|null |
46
|
|
|
*/ |
47
|
|
|
protected $_exitCode; |
48
|
|
|
|
49
|
|
|
/** |
50
|
|
|
* Console output stub |
51
|
|
|
* |
52
|
|
|
* @var \Cake\TestSuite\Stub\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject|null |
53
|
|
|
*/ |
54
|
|
|
protected $_out; |
55
|
|
|
|
56
|
|
|
/** |
57
|
|
|
* Console error output stub |
58
|
|
|
* |
59
|
|
|
* @var \Cake\TestSuite\Stub\ConsoleOutput|\PHPUnit_Framework_MockObject_MockObject|null |
60
|
|
|
*/ |
61
|
|
|
protected $_err; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* Console input mock |
65
|
|
|
* |
66
|
|
|
* @var \Cake\Console\ConsoleInput|\PHPUnit_Framework_MockObject_MockObject|null |
67
|
|
|
*/ |
68
|
|
|
protected $_in; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Runs cli integration test |
72
|
|
|
* |
73
|
|
|
* @param string $command Command to run |
74
|
|
|
* @param array $input Input values to pass to an interactive shell |
75
|
|
|
* @return void |
76
|
|
|
*/ |
77
|
|
|
public function exec($command, array $input = []) |
78
|
|
|
{ |
79
|
|
|
$runner = $this->makeRunner(); |
80
|
|
|
|
81
|
|
|
$this->_out = new ConsoleOutput(); |
82
|
|
|
$this->_err = new ConsoleOutput(); |
83
|
|
|
$this->_in = $this->getMockBuilder(ConsoleInput::class) |
|
|
|
|
84
|
|
|
->disableOriginalConstructor() |
85
|
|
|
->setMethods(['read']) |
86
|
|
|
->getMock(); |
87
|
|
|
|
88
|
|
|
$i = 0; |
89
|
|
|
foreach ($input as $in) { |
90
|
|
|
$this->_in |
91
|
|
|
->expects($this->at($i++)) |
|
|
|
|
92
|
|
|
->method('read') |
93
|
|
|
->will($this->returnValue($in)); |
|
|
|
|
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$args = $this->commandStringToArgs("cake $command"); |
97
|
|
|
$io = new ConsoleIo($this->_out, $this->_err, $this->_in); |
98
|
|
|
|
99
|
|
|
try { |
100
|
|
|
$this->_exitCode = $runner->run($args, $io); |
101
|
|
|
} catch (StopException $exception) { |
102
|
|
|
$this->_exitCode = $exception->getCode(); |
103
|
|
|
} |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Cleans state to get ready for the next test |
108
|
|
|
* |
109
|
|
|
* @after |
110
|
|
|
* @return void |
111
|
|
|
*/ |
112
|
|
|
public function cleanupConsoleTrait() |
113
|
|
|
{ |
114
|
|
|
$this->_exitCode = null; |
115
|
|
|
$this->_out = null; |
116
|
|
|
$this->_err = null; |
117
|
|
|
$this->_in = null; |
118
|
|
|
$this->_useCommandRunner = false; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* Set this test case to use the CommandRunner rather than the legacy |
123
|
|
|
* ShellDispatcher |
124
|
|
|
* |
125
|
|
|
* @return void |
126
|
|
|
*/ |
127
|
|
|
public function useCommandRunner() |
128
|
|
|
{ |
129
|
|
|
$this->_useCommandRunner = true; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Asserts shell exited with the expected code |
134
|
|
|
* |
135
|
|
|
* @param int $expected Expected exit code |
136
|
|
|
* @param string $message Failure message |
137
|
|
|
* @return void |
138
|
|
|
*/ |
139
|
|
|
public function assertExitCode($expected, $message = '') |
140
|
|
|
{ |
141
|
|
|
$this->assertThat($expected, new ExitCode($this->_exitCode), $message); |
|
|
|
|
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Asserts that `stdout` is empty |
146
|
|
|
* |
147
|
|
|
* @param string $message The message to output when the assertion fails. |
148
|
|
|
* @return void |
149
|
|
|
*/ |
150
|
|
|
public function assertOutputEmpty($message = '') |
151
|
|
|
{ |
152
|
|
|
$this->assertThat(null, new ContentsEmpty($this->_out->messages(), 'output'), $message); |
|
|
|
|
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* Asserts `stdout` contains expected output |
157
|
|
|
* |
158
|
|
|
* @param string $expected Expected output |
159
|
|
|
* @param string $message Failure message |
160
|
|
|
* @return void |
161
|
|
|
*/ |
162
|
|
|
public function assertOutputContains($expected, $message = '') |
163
|
|
|
{ |
164
|
|
|
$this->assertThat($expected, new ContentsContain($this->_out->messages(), 'output'), $message); |
|
|
|
|
165
|
|
|
} |
166
|
|
|
/** |
167
|
|
|
* Asserts `stdout` does not contain expected output |
168
|
|
|
* |
169
|
|
|
* @param string $expected Expected output |
170
|
|
|
* @param string $message Failure message |
171
|
|
|
* @return void |
172
|
|
|
*/ |
173
|
|
|
public function assertOutputNotContains($expected, $message = '') |
174
|
|
|
{ |
175
|
|
|
$this->assertThat($expected, new ContentsNotContain($this->_out->messages(), 'output'), $message); |
|
|
|
|
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Asserts `stdout` contains expected regexp |
180
|
|
|
* |
181
|
|
|
* @param string $pattern Expected pattern |
182
|
|
|
* @param string $message Failure message |
183
|
|
|
* @return void |
184
|
|
|
*/ |
185
|
|
|
public function assertOutputRegExp($pattern, $message = '') |
186
|
|
|
{ |
187
|
|
|
$this->assertThat($pattern, new ContentsRegExp($this->_out->messages(), 'output'), $message); |
|
|
|
|
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Check that a row of cells exists in the output. |
192
|
|
|
* |
193
|
|
|
* @param array $row Row of cells to ensure exist in the output. |
194
|
|
|
* @param string $message Failure message. |
195
|
|
|
* @return void |
196
|
|
|
*/ |
197
|
|
|
protected function assertOutputContainsRow(array $row, $message = '') |
198
|
|
|
{ |
199
|
|
|
$this->assertThat($row, new ContentsContainRow($this->_out->messages(), 'output'), $message); |
|
|
|
|
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Asserts `stderr` contains expected output |
204
|
|
|
* |
205
|
|
|
* @param string $expected Expected output |
206
|
|
|
* @param string $message Failure message |
207
|
|
|
* @return void |
208
|
|
|
*/ |
209
|
|
|
public function assertErrorContains($expected, $message = '') |
210
|
|
|
{ |
211
|
|
|
$this->assertThat($expected, new ContentsContain($this->_err->messages(), 'error output'), $message); |
|
|
|
|
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Asserts `stderr` contains expected regexp |
216
|
|
|
* |
217
|
|
|
* @param string $pattern Expected pattern |
218
|
|
|
* @param string $message Failure message |
219
|
|
|
* @return void |
220
|
|
|
*/ |
221
|
|
|
public function assertErrorRegExp($pattern, $message = '') |
222
|
|
|
{ |
223
|
|
|
$this->assertThat($pattern, new ContentsRegExp($this->_err->messages(), 'error output'), $message); |
|
|
|
|
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Asserts that `stderr` is empty |
228
|
|
|
* |
229
|
|
|
* @param string $message The message to output when the assertion fails. |
230
|
|
|
* @return void |
231
|
|
|
*/ |
232
|
|
|
public function assertErrorEmpty($message = '') |
233
|
|
|
{ |
234
|
|
|
$this->assertThat(null, new ContentsEmpty($this->_err->messages(), 'error output'), $message); |
|
|
|
|
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Builds the appropriate command dispatcher |
239
|
|
|
* |
240
|
|
|
* @return CommandRunner|LegacyCommandRunner |
241
|
|
|
*/ |
242
|
|
|
protected function makeRunner() |
243
|
|
|
{ |
244
|
|
|
if ($this->_useCommandRunner) { |
245
|
|
|
$applicationClassName = Configure::read('App.namespace') . '\Application'; |
246
|
|
|
|
247
|
|
|
return new CommandRunner(new $applicationClassName(CONFIG)); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return new LegacyCommandRunner(); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* Creates an $argv array from a command string |
255
|
|
|
* |
256
|
|
|
* @param string $command Command string |
257
|
|
|
* @return array |
258
|
|
|
*/ |
259
|
|
|
protected function commandStringToArgs($command) |
260
|
|
|
{ |
261
|
|
|
$charCount = strlen($command); |
262
|
|
|
$argv = []; |
263
|
|
|
$arg = ''; |
264
|
|
|
$inDQuote = false; |
265
|
|
|
$inSQuote = false; |
266
|
|
|
for ($i = 0; $i < $charCount; $i++) { |
267
|
|
|
$char = substr($command, $i, 1); |
268
|
|
|
|
269
|
|
|
// end of argument |
270
|
|
|
if ($char === ' ' && !$inDQuote && !$inSQuote) { |
271
|
|
|
if (strlen($arg)) { |
272
|
|
|
$argv[] = $arg; |
273
|
|
|
} |
274
|
|
|
$arg = ''; |
275
|
|
|
continue; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
// exiting single quote |
279
|
|
|
if ($inSQuote && $char === "'") { |
280
|
|
|
$inSQuote = false; |
281
|
|
|
continue; |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
// exiting double quote |
285
|
|
|
if ($inDQuote && $char === '"') { |
286
|
|
|
$inDQuote = false; |
287
|
|
|
continue; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
// entering double quote |
291
|
|
|
if ($char === '"' && !$inSQuote) { |
292
|
|
|
$inDQuote = true; |
293
|
|
|
continue; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
// entering single quote |
297
|
|
|
if ($char === "'" && !$inDQuote) { |
298
|
|
|
$inSQuote = true; |
299
|
|
|
continue; |
300
|
|
|
} |
301
|
|
|
|
302
|
|
|
$arg .= $char; |
303
|
|
|
} |
304
|
|
|
$argv[] = $arg; |
305
|
|
|
|
306
|
|
|
return $argv; |
307
|
|
|
} |
308
|
|
|
} |
309
|
|
|
|
This check looks for methods that are used by a trait but not required by it.
To illustrate, let’s look at the following code example
The trait
Idable
provides a methodequalsId
that in turn relies on the methodgetId()
. If this method does not exist on a class mixing in this trait, the method will fail.Adding the
getId()
as an abstract method to the trait will make sure it is available.