forked from wintercms/winter
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWinterTest.php
More file actions
254 lines (214 loc) · 8.06 KB
/
WinterTest.php
File metadata and controls
254 lines (214 loc) · 8.06 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
<?php namespace System\Console;
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\Process;
use System\Classes\PluginManager;
use Winter\Storm\Exception\ApplicationException;
/**
* Console command to run tests for plugins or the Winter CMS core.
*
* If a plugin is provided, this command will search for a `phpunit.xml` file inside the plugin's directory and run its tests.
*
* @package winter\wn-system-module
*/
class WinterTest extends Command
{
/**
* @var string The console command name.
*/
protected $name = 'winter:test';
/**
* @var string The console command signature as ignoreValidationErrors causes options not to be registered.
*/
protected $signature = 'winter:test {?--p|plugin=} {?--c|configuration=} {?--o|core}';
/**
* @var string The console command description.
*/
protected $description = 'Run tests for the Winter CMS core or an existing plugin.';
/**
* @var ?string Path to phpunit binary
*/
protected $phpUnitExec = null;
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
/**
* Ignore validation errors as option proxying is used by this command
* @see https://github.com/nunomaduro/collision/blob/stable/src/Adapters/Laravel/Commands/TestCommand.php
*/
$this->ignoreValidationErrors();
}
/**
* Execute the console command.
*
* @throws ApplicationException
* @return int|void
*/
public function handle()
{
$arguments = $this->getAdditionalArguments();
if (($config = $this->option('configuration')) && file_exists($config)) {
return $this->execPhpUnit($config, $arguments);
}
$configs = $this->getPhpUnitConfigs();
if ($this->option('core')) {
if (!$configs['core']) {
throw new ApplicationException("Unable to find the core's phpunit.xml file. Try downloading it from GitHub.");
}
$this->info('Running tests for: Winter CMS core');
return $this->execPhpUnit($configs['core'], $arguments);
}
if ($plugin = $this->option('plugin')) {
if (!isset($configs['plugins'][strtolower($plugin)])) {
throw new ApplicationException(sprintf("Unable to find %s\'s phpunit.xml file", $plugin));
}
$this->info('Running tests for plugin: ' . PluginManager::instance()->normalizeIdentifier($plugin));
return $this->execPhpUnit($configs['plugins'][strtolower($plugin)], $arguments);
}
$exitCode = 0;
foreach (['core', 'plugins'] as $type) {
if (is_array($configs[$type])) {
foreach ($configs[$type] as $plugin => $config) {
$this->info('Running tests for plugin: ' . PluginManager::instance()->normalizeIdentifier($plugin));
$exit = $this->execPhpUnit($config, $arguments);
$exitCode = $exitCode === 0 ? $exit : $exitCode;
}
continue;
}
$this->info('Running tests for Winter CMS: ' . $type);
$exit = $this->execPhpUnit($configs[$type], $arguments);
$exitCode = $exitCode === 0 ? $exit : $exitCode;
}
return $exitCode;
}
/**
* Get the console command options.
*/
protected function getOptions(): array
{
return [
['plugin', 'p', InputOption::VALUE_OPTIONAL, 'The name of the plugin. Ex: AuthorName.PluginName', null],
['configuration', 'c', InputOption::VALUE_OPTIONAL, 'The path to a PHPUnit XML config file', null],
['core', 'o', InputOption::VALUE_NONE, 'Run the Winter CMS core tests'],
];
}
/**
* Execute a phpunit test
*
* @param string $config Path to configuration file
* @param array $args Array of params for PHPUnit
* @return int Exit code from process
*/
protected function execPhpUnit(string $config, array $args): int
{
// Find and bind the phpunit executable
if (!$this->phpUnitExec) {
$this->phpUnitExec = (new ExecutableFinder())
->find('phpunit', base_path('vendor/bin/phpunit'), [base_path('vendor')]);
}
$process = new Process(
array_merge([$this->phpUnitExec, '--configuration=' . $config], $args),
dirname($config),
null,
null
);
// Attempt to set tty mode, catch and warn with the exception message if unsupported
try {
$process->setTty(true);
} catch (\Throwable $e) {
$this->warn($e->getMessage());
}
try {
return $process->run(function ($type, $line) {
$this->output->write($line);
});
} catch (ProcessSignaledException $e) {
if (extension_loaded('pcntl') && $e->getSignal() !== SIGINT) {
throw $e;
}
return 1;
}
}
/**
* Find all PHPUnit config files (core, lib, plugins)
*/
protected function getPhpUnitConfigs(): array
{
$configs = [
'core' => $this->getPhpUnitXmlFile(base_path()),
'plugins' => []
];
foreach (PluginManager::instance()->getPlugins() as $plugin) {
if ($path = $this->getPhpUnitXmlFile($plugin->getPluginPath())) {
$configs['plugins'][strtolower($plugin->getPluginIdentifier())] = $path;
}
}
return $configs;
}
/**
* Search for the config file to use.
* Priority order is: phpunit.xml, phpunit.xml.dist
*/
protected function getPhpUnitXmlFile(string $path): ?string
{
// If a phpunit.xml file exists, returns its path
$distFilePath = $path . DIRECTORY_SEPARATOR . 'phpunit.xml';
if (file_exists($distFilePath)) {
return $distFilePath;
}
// Fallback to phpunit.xml.dist file path if it exists
$configFilePath = $path . DIRECTORY_SEPARATOR . 'phpunit.xml.dist';
if (file_exists($configFilePath)) {
return $configFilePath;
}
return null;
}
/**
* Strips out commands arguments and options in order to return arguments/options for PHPUnit.
*/
protected function getAdditionalArguments(): array
{
$arguments = $_SERVER['argv'];
// First two are always "artisan" and "winter:test"
$arguments = array_slice($arguments, 2);
// If nothing to do then just return
if (!count($arguments)) {
return $arguments;
}
// Get the arguments provided by this command
foreach ($this->getOptions() as $argument) {
// For position 0 & 1, pass their names with appropriate dashes
for ($i = 0; $i < 2; $i++) {
$arguments = $this->removeArgument($arguments, str_repeat('-', 2 - $i) . $argument[$i]);
}
}
return $arguments;
}
/**
* Removes flags from argument list and their value if present
*/
protected function removeArgument(array $arguments, string $remove): array
{
// find args that have trailing chars
$key = array_values(preg_grep("/^({$remove}|{$remove}=).*/i", $arguments));
$remove = (isset($key[0])) ? $key[0] : $remove;
// find the position of arguments to remove
if (($position = array_search($remove, $arguments)) === false) {
return $arguments;
}
// remove argument
unset($arguments[$position]);
// if the next item in the array is not a flag, consider it a value of the removed argument
if (isset($arguments[$position + 1]) && substr($arguments[$position + 1], 0, 1) !== '-') {
unset($arguments[$position + 1]);
}
return array_values($arguments);
}
}