1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Codeception; |
4
|
|
|
|
5
|
|
|
use Codeception\Exception\ConfigurationException; |
6
|
|
|
use Codeception\Lib\ParamsLoader; |
7
|
|
|
use Codeception\Util\Autoload; |
8
|
|
|
use Codeception\Util\Template; |
9
|
|
|
use Symfony\Component\Finder\Finder; |
10
|
|
|
use Symfony\Component\Finder\SplFileInfo; |
11
|
|
|
use Symfony\Component\Yaml\Exception\ParseException; |
12
|
|
|
use Symfony\Component\Yaml\Yaml; |
13
|
|
|
|
14
|
|
|
class Configuration |
15
|
|
|
{ |
16
|
|
|
protected static $suites = []; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* @var array Current configuration |
20
|
|
|
*/ |
21
|
|
|
protected static $config = null; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @var array environmental files configuration cache |
25
|
|
|
*/ |
26
|
|
|
protected static $envConfig = []; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var string Directory containing main configuration file. |
30
|
|
|
* @see self::projectDir() |
31
|
|
|
*/ |
32
|
|
|
protected static $dir = null; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var string Current project output directory. |
36
|
|
|
*/ |
37
|
|
|
protected static $outputDir = null; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var string Current project data directory. This directory is used to hold |
41
|
|
|
* sql dumps and other things needed for current project tests. |
42
|
|
|
*/ |
43
|
|
|
protected static $dataDir = null; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* @var string Directory with test support files like Actors, Helpers, PageObjects, etc |
47
|
|
|
*/ |
48
|
|
|
protected static $supportDir = null; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @var string Directory containing environment configuration files. |
52
|
|
|
*/ |
53
|
|
|
protected static $envsDir = null; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @var string Directory containing tests and suites of the current project. |
57
|
|
|
*/ |
58
|
|
|
protected static $testsDir = null; |
59
|
|
|
|
60
|
|
|
public static $lock = false; |
61
|
|
|
|
62
|
|
|
protected static $di; |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* @var array Default config |
66
|
|
|
*/ |
67
|
|
|
public static $defaultConfig = [ |
68
|
|
|
'actor_suffix'=> 'Tester', |
69
|
|
|
'namespace' => '', |
70
|
|
|
'include' => [], |
71
|
|
|
'paths' => [], |
72
|
|
|
'extends' => null, |
73
|
|
|
'suites' => [], |
74
|
|
|
'modules' => [], |
75
|
|
|
'extensions' => [ |
76
|
|
|
'enabled' => [], |
77
|
|
|
'config' => [], |
78
|
|
|
'commands' => [], |
79
|
|
|
], |
80
|
|
|
'reporters' => [ |
81
|
|
|
'xml' => 'Codeception\PHPUnit\Log\JUnit', |
82
|
|
|
'html' => 'Codeception\PHPUnit\ResultPrinter\HTML', |
83
|
|
|
'report' => 'Codeception\PHPUnit\ResultPrinter\Report', |
84
|
|
|
'tap' => 'PHPUnit\Util\Log\TAP', |
85
|
|
|
'json' => 'PHPUnit\Util\Log\JSON', |
86
|
|
|
], |
87
|
|
|
'groups' => [], |
88
|
|
|
'settings' => [ |
89
|
|
|
'colors' => true, |
90
|
|
|
'bootstrap' => false, |
91
|
|
|
'strict_xml' => false, |
92
|
|
|
'lint' => true, |
93
|
|
|
'backup_globals' => true, |
94
|
|
|
'log_incomplete_skipped' => false, |
95
|
|
|
'report_useless_tests' => false, |
96
|
|
|
'disallow_test_output' => false, |
97
|
|
|
'be_strict_about_changes_to_global_state' => false |
98
|
|
|
], |
99
|
|
|
'coverage' => [], |
100
|
|
|
'params' => [], |
101
|
|
|
'gherkin' => [] |
102
|
|
|
]; |
103
|
|
|
|
104
|
|
|
public static $defaultSuiteSettings = [ |
105
|
|
|
'actor' => null, |
106
|
|
|
'class_name' => null, // Codeception <2.3 compatibility |
107
|
|
|
'modules' => [ |
108
|
|
|
'enabled' => [], |
109
|
|
|
'config' => [], |
110
|
|
|
'depends' => [] |
111
|
|
|
], |
112
|
|
|
'path' => null, |
113
|
|
|
'extends' => null, |
114
|
|
|
'namespace' => null, |
115
|
|
|
'groups' => [], |
116
|
|
|
'formats' => [], |
117
|
|
|
'shuffle' => false, |
118
|
|
|
'extensions' => [ // suite extensions |
119
|
|
|
'enabled' => [], |
120
|
|
|
'config' => [], |
121
|
|
|
], |
122
|
|
|
'error_level' => 'E_ALL & ~E_STRICT & ~E_DEPRECATED', |
123
|
|
|
]; |
124
|
|
|
|
125
|
|
|
protected static $params; |
126
|
|
|
|
127
|
|
|
/** |
128
|
|
|
* Loads global config file which is `codeception.yml` by default. |
129
|
|
|
* When config is already loaded - returns it. |
130
|
|
|
* |
131
|
|
|
* @param null $configFile |
132
|
|
|
* @return array |
133
|
|
|
* @throws Exception\ConfigurationException |
134
|
|
|
*/ |
135
|
|
|
public static function config($configFile = null) |
136
|
|
|
{ |
137
|
|
|
if (!$configFile && self::$config) { |
|
|
|
|
138
|
|
|
return self::$config; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
if (self::$config && self::$lock) { |
|
|
|
|
142
|
|
|
return self::$config; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
if ($configFile === null) { |
146
|
|
|
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'codeception.yml'; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
if (is_dir($configFile)) { |
150
|
|
|
$configFile = $configFile . DIRECTORY_SEPARATOR . 'codeception.yml'; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
$dir = realpath(dirname($configFile)); |
154
|
|
|
self::$dir = $dir; |
155
|
|
|
|
156
|
|
|
$configDistFile = $dir . DIRECTORY_SEPARATOR . 'codeception.dist.yml'; |
157
|
|
|
|
158
|
|
|
if (!(file_exists($configDistFile) || file_exists($configFile))) { |
159
|
|
|
throw new ConfigurationException("Configuration file could not be found.\nRun `bootstrap` to initialize Codeception.", 404); |
160
|
|
|
} |
161
|
|
|
|
162
|
|
|
// Preload config to retrieve params such that they are applied to codeception config file below |
163
|
|
|
$tempConfig = self::$defaultConfig; |
164
|
|
|
|
165
|
|
|
$distConfigContents = ""; |
166
|
|
|
if (file_exists($configDistFile)) { |
167
|
|
|
$distConfigContents = file_get_contents($configDistFile); |
168
|
|
|
$tempConfig = self::mergeConfigs($tempConfig, self::getConfFromContents($distConfigContents, $configDistFile)); |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
$configContents = ""; |
172
|
|
|
if (file_exists($configFile)) { |
173
|
|
|
$configContents = file_get_contents($configFile); |
174
|
|
|
$tempConfig = self::mergeConfigs($tempConfig, self::getConfFromContents($configContents, $configFile)); |
175
|
|
|
} |
176
|
|
|
self::prepareParams($tempConfig); |
177
|
|
|
|
178
|
|
|
// load config using params |
179
|
|
|
$config = self::mergeConfigs(self::$defaultConfig, self::getConfFromContents($distConfigContents, $configDistFile)); |
180
|
|
|
$config = self::mergeConfigs($config, self::getConfFromContents($configContents, $configFile)); |
181
|
|
|
|
182
|
|
|
if ($config == self::$defaultConfig) { |
183
|
|
|
throw new ConfigurationException("Configuration file is invalid"); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
// we check for the "extends" key in the yml file |
187
|
|
|
if (isset($config['extends'])) { |
188
|
|
|
// and now we search for the file |
189
|
|
|
$presetFilePath = codecept_absolute_path($config['extends']); |
190
|
|
|
if (file_exists($presetFilePath)) { |
191
|
|
|
// and merge it with our configuration file |
192
|
|
|
$config = self::mergeConfigs(self::getConfFromFile($presetFilePath), $config); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
self::$config = $config; |
197
|
|
|
|
198
|
|
|
// compatibility with suites created by Codeception < 2.3.0 |
199
|
|
View Code Duplication |
if (!isset($config['paths']['output']) and isset($config['paths']['log'])) { |
|
|
|
|
200
|
|
|
$config['paths']['output'] = $config['paths']['log']; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
if (isset(self::$config['actor'])) { |
204
|
|
|
self::$config['actor_suffix'] = self::$config['actor']; // old compatibility |
205
|
|
|
} |
206
|
|
|
|
207
|
|
View Code Duplication |
if (!isset($config['paths']['support']) and isset($config['paths']['helpers'])) { |
|
|
|
|
208
|
|
|
$config['paths']['support'] = $config['paths']['helpers']; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if (!isset($config['paths']['output'])) { |
212
|
|
|
throw new ConfigurationException('Output path is not defined by key "paths: output"'); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
self::$outputDir = $config['paths']['output']; |
216
|
|
|
|
217
|
|
|
// fill up includes with wildcard expansions |
218
|
|
|
$config['include'] = self::expandWildcardedIncludes($config['include']); |
219
|
|
|
|
220
|
|
|
// config without tests, for inclusion of other configs |
221
|
|
|
if (count($config['include'])) { |
222
|
|
|
self::$config = $config; |
223
|
|
|
if (!isset($config['paths']['tests'])) { |
224
|
|
|
return $config; |
225
|
|
|
} |
226
|
|
|
} |
227
|
|
|
|
228
|
|
|
if (!isset($config['paths']['tests'])) { |
229
|
|
|
throw new ConfigurationException( |
230
|
|
|
'Tests directory is not defined in Codeception config by key "paths: tests:"' |
231
|
|
|
); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
if (!isset($config['paths']['data'])) { |
235
|
|
|
throw new ConfigurationException('Data path is not defined Codeception config by key "paths: data"'); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
if (!isset($config['paths']['support'])) { |
239
|
|
|
throw new ConfigurationException('Helpers path is not defined by key "paths: support"'); |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
self::$dataDir = $config['paths']['data']; |
243
|
|
|
self::$supportDir = $config['paths']['support']; |
244
|
|
|
self::$testsDir = $config['paths']['tests']; |
245
|
|
|
|
246
|
|
|
if (isset($config['paths']['envs'])) { |
247
|
|
|
self::$envsDir = $config['paths']['envs']; |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
Autoload::addNamespace(self::$config['namespace'], self::supportDir()); |
251
|
|
|
self::loadBootstrap($config['settings']['bootstrap']); |
252
|
|
|
self::loadSuites(); |
253
|
|
|
|
254
|
|
|
return $config; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
protected static function loadBootstrap($bootstrap) |
258
|
|
|
{ |
259
|
|
|
if (!$bootstrap) { |
260
|
|
|
return; |
261
|
|
|
} |
262
|
|
|
$bootstrap = self::$dir . DIRECTORY_SEPARATOR . self::$testsDir . DIRECTORY_SEPARATOR . $bootstrap; |
263
|
|
|
if (file_exists($bootstrap)) { |
264
|
|
|
include_once $bootstrap; |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
protected static function loadSuites() |
269
|
|
|
{ |
270
|
|
|
$suites = Finder::create() |
271
|
|
|
->files() |
272
|
|
|
->name('*.{suite,suite.dist}.yml') |
273
|
|
|
->in(self::$dir . DIRECTORY_SEPARATOR . self::$testsDir) |
274
|
|
|
->depth('< 1') |
275
|
|
|
->sortByName(); |
276
|
|
|
|
277
|
|
|
self::$suites = []; |
278
|
|
|
|
279
|
|
|
foreach (array_keys(self::$config['suites']) as $suite) { |
280
|
|
|
self::$suites[$suite] = $suite; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** @var SplFileInfo $suite */ |
284
|
|
|
foreach ($suites as $suite) { |
285
|
|
|
preg_match('~(.*?)(\.suite|\.suite\.dist)\.yml~', $suite->getFilename(), $matches); |
286
|
|
|
self::$suites[$matches[1]] = $matches[1]; |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Returns suite configuration. Requires suite name and global config used (Configuration::config) |
292
|
|
|
* |
293
|
|
|
* @param string $suite |
294
|
|
|
* @param array $config |
295
|
|
|
* @return array |
296
|
|
|
* @throws \Exception |
297
|
|
|
*/ |
298
|
|
|
public static function suiteSettings($suite, $config) |
299
|
|
|
{ |
300
|
|
|
// cut namespace name from suite name |
301
|
|
|
if ($suite != $config['namespace'] && substr($suite, 0, strlen($config['namespace'])) == $config['namespace']) { |
302
|
|
|
$suite = substr($suite, strlen($config['namespace'])); |
303
|
|
|
} |
304
|
|
|
|
305
|
|
|
if (!in_array($suite, self::$suites)) { |
306
|
|
|
throw new ConfigurationException("Suite $suite was not loaded"); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
// load global config |
310
|
|
|
$globalConf = $config['settings']; |
311
|
|
|
foreach (['modules', 'coverage', 'namespace', 'groups', 'env', 'gherkin', 'extensions'] as $key) { |
312
|
|
|
if (isset($config[$key])) { |
313
|
|
|
$globalConf[$key] = $config[$key]; |
314
|
|
|
} |
315
|
|
|
} |
316
|
|
|
$settings = self::mergeConfigs(self::$defaultSuiteSettings, $globalConf); |
317
|
|
|
|
318
|
|
|
// load suite config |
319
|
|
|
$settings = self::loadSuiteConfig($suite, $config['paths']['tests'], $settings); |
320
|
|
|
// load from environment configs |
321
|
|
|
if (isset($config['paths']['envs'])) { |
322
|
|
|
$envConf = self::loadEnvConfigs(self::$dir . DIRECTORY_SEPARATOR . $config['paths']['envs']); |
323
|
|
|
$settings = self::mergeConfigs($settings, $envConf); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
if (!$settings['actor']) { |
327
|
|
|
// Codeception 2.2 compatibility |
328
|
|
|
$settings['actor'] = $settings['class_name']; |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
if (!$settings['path']) { |
332
|
|
|
// take a suite path from its name |
333
|
|
|
$settings['path'] = $suite; |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
$config['paths']['tests'] = str_replace('/', DIRECTORY_SEPARATOR, $config['paths']['tests']); |
337
|
|
|
|
338
|
|
|
$settings['path'] = self::$dir . DIRECTORY_SEPARATOR . $config['paths']['tests'] |
339
|
|
|
. DIRECTORY_SEPARATOR . $settings['path'] . DIRECTORY_SEPARATOR; |
340
|
|
|
|
341
|
|
|
|
342
|
|
|
|
343
|
|
|
return $settings; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Loads environments configuration from set directory |
348
|
|
|
* |
349
|
|
|
* @param string $path path to the directory |
350
|
|
|
* @return array |
351
|
|
|
*/ |
352
|
|
|
protected static function loadEnvConfigs($path) |
353
|
|
|
{ |
354
|
|
|
if (isset(self::$envConfig[$path])) { |
355
|
|
|
return self::$envConfig[$path]; |
356
|
|
|
} |
357
|
|
|
if (!is_dir($path)) { |
358
|
|
|
self::$envConfig[$path] = []; |
359
|
|
|
return self::$envConfig[$path]; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
$envFiles = Finder::create() |
363
|
|
|
->files() |
364
|
|
|
->name('*.yml') |
365
|
|
|
->in($path) |
366
|
|
|
->depth('< 2'); |
367
|
|
|
|
368
|
|
|
$envConfig = []; |
369
|
|
|
/** @var SplFileInfo $envFile */ |
370
|
|
|
foreach ($envFiles as $envFile) { |
371
|
|
|
$env = str_replace(['.dist.yml', '.yml'], '', $envFile->getFilename()); |
372
|
|
|
$envConfig[$env] = []; |
373
|
|
|
$envPath = $path; |
374
|
|
|
if ($envFile->getRelativePath()) { |
375
|
|
|
$envPath .= DIRECTORY_SEPARATOR . $envFile->getRelativePath(); |
376
|
|
|
} |
377
|
|
|
foreach (['.dist.yml', '.yml'] as $suffix) { |
378
|
|
|
$envConf = self::getConfFromFile($envPath . DIRECTORY_SEPARATOR . $env . $suffix, null); |
379
|
|
|
if ($envConf === null) { |
380
|
|
|
continue; |
381
|
|
|
} |
382
|
|
|
$envConfig[$env] = self::mergeConfigs($envConfig[$env], $envConf); |
383
|
|
|
} |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
self::$envConfig[$path] = ['env' => $envConfig]; |
387
|
|
|
return self::$envConfig[$path]; |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
/** |
391
|
|
|
* Loads configuration from Yaml data |
392
|
|
|
* |
393
|
|
|
* @param string $contents Yaml config file contents |
394
|
|
|
* @param string $filename which is supposed to be loaded |
395
|
|
|
* @return array |
396
|
|
|
* @throws ConfigurationException |
397
|
|
|
*/ |
398
|
|
|
protected static function getConfFromContents($contents, $filename = '(.yml)') |
399
|
|
|
{ |
400
|
|
|
if (self::$params) { |
401
|
|
|
$template = new Template($contents, '%', '%'); |
402
|
|
|
$template->setVars(self::$params); |
403
|
|
|
$contents = $template->produce(); |
404
|
|
|
} |
405
|
|
|
|
406
|
|
|
try { |
407
|
|
|
return Yaml::parse($contents); |
408
|
|
|
} catch (ParseException $exception) { |
409
|
|
|
throw new ConfigurationException( |
410
|
|
|
sprintf( |
411
|
|
|
"Error loading Yaml config from `%s`\n \n%s\nRead more about Yaml format https://goo.gl/9UPuEC", |
412
|
|
|
$filename, |
413
|
|
|
$exception->getMessage() |
414
|
|
|
) |
415
|
|
|
); |
416
|
|
|
} |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
/** |
420
|
|
|
* Loads configuration from Yaml file or returns given value if the file doesn't exist |
421
|
|
|
* |
422
|
|
|
* @param string $filename filename |
423
|
|
|
* @param mixed $nonExistentValue value used if filename is not found |
424
|
|
|
* @return array |
425
|
|
|
*/ |
426
|
|
|
protected static function getConfFromFile($filename, $nonExistentValue = []) |
427
|
|
|
{ |
428
|
|
|
if (file_exists($filename)) { |
429
|
|
|
$yaml = file_get_contents($filename); |
430
|
|
|
return self::getConfFromContents($yaml, $filename); |
431
|
|
|
} |
432
|
|
|
return $nonExistentValue; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
/** |
436
|
|
|
* Returns all possible suite configurations according environment rules. |
437
|
|
|
* Suite configurations will contain `current_environment` key which specifies what environment used. |
438
|
|
|
* |
439
|
|
|
* @param $suite |
440
|
|
|
* @return array |
441
|
|
|
*/ |
442
|
|
|
public static function suiteEnvironments($suite) |
443
|
|
|
{ |
444
|
|
|
$settings = self::suiteSettings($suite, self::config()); |
445
|
|
|
|
446
|
|
|
if (!isset($settings['env']) || !is_array($settings['env'])) { |
447
|
|
|
return []; |
448
|
|
|
} |
449
|
|
|
|
450
|
|
|
$environments = []; |
451
|
|
|
|
452
|
|
|
foreach ($settings['env'] as $env => $envConfig) { |
453
|
|
|
$environments[$env] = $envConfig ? self::mergeConfigs($settings, $envConfig) : $settings; |
454
|
|
|
$environments[$env]['current_environment'] = $env; |
455
|
|
|
} |
456
|
|
|
|
457
|
|
|
return $environments; |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
public static function suites() |
461
|
|
|
{ |
462
|
|
|
return self::$suites; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
/** |
466
|
|
|
* Return list of enabled modules according suite config. |
467
|
|
|
* |
468
|
|
|
* @param array $settings suite settings |
469
|
|
|
* @return array |
470
|
|
|
*/ |
471
|
|
|
public static function modules($settings) |
472
|
|
|
{ |
473
|
|
|
return array_filter( |
474
|
|
|
array_map( |
475
|
|
|
function ($m) { |
476
|
|
|
return is_array($m) ? key($m) : $m; |
477
|
|
|
}, |
478
|
|
|
$settings['modules']['enabled'], |
479
|
|
|
array_keys($settings['modules']['enabled']) |
480
|
|
|
), |
481
|
|
|
function ($m) use ($settings) { |
482
|
|
|
if (!isset($settings['modules']['disabled'])) { |
483
|
|
|
return true; |
484
|
|
|
} |
485
|
|
|
return !in_array($m, $settings['modules']['disabled']); |
486
|
|
|
} |
487
|
|
|
); |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
public static function isExtensionEnabled($extensionName) |
491
|
|
|
{ |
492
|
|
|
return isset(self::$config['extensions'], self::$config['extensions']['enabled']) |
493
|
|
|
&& in_array($extensionName, self::$config['extensions']['enabled']); |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
/** |
497
|
|
|
* Returns current path to `_data` dir. |
498
|
|
|
* Use it to store database fixtures, sql dumps, or other files required by your tests. |
499
|
|
|
* |
500
|
|
|
* @return string |
501
|
|
|
*/ |
502
|
|
|
public static function dataDir() |
503
|
|
|
{ |
504
|
|
|
return self::$dir . DIRECTORY_SEPARATOR . self::$dataDir . DIRECTORY_SEPARATOR; |
505
|
|
|
} |
506
|
|
|
|
507
|
|
|
/** |
508
|
|
|
* Return current path to `_helpers` dir. |
509
|
|
|
* Helpers are custom modules. |
510
|
|
|
* |
511
|
|
|
* @return string |
512
|
|
|
*/ |
513
|
|
|
public static function supportDir() |
514
|
|
|
{ |
515
|
|
|
return self::$dir . DIRECTORY_SEPARATOR . self::$supportDir . DIRECTORY_SEPARATOR; |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
/** |
519
|
|
|
* Returns actual path to current `_output` dir. |
520
|
|
|
* Use it in Helpers or Groups to save result or temporary files. |
521
|
|
|
* |
522
|
|
|
* @return string |
523
|
|
|
* @throws Exception\ConfigurationException |
524
|
|
|
*/ |
525
|
|
|
public static function outputDir() |
526
|
|
|
{ |
527
|
|
|
if (!self::$outputDir) { |
528
|
|
|
throw new ConfigurationException("Path for output not specified. Please, set output path in global config"); |
529
|
|
|
} |
530
|
|
|
|
531
|
|
|
$dir = self::$outputDir . DIRECTORY_SEPARATOR; |
532
|
|
|
if (strcmp(self::$outputDir[0], "/") !== 0) { |
533
|
|
|
$dir = self::$dir . DIRECTORY_SEPARATOR . $dir; |
534
|
|
|
} |
535
|
|
|
|
536
|
|
|
if (!file_exists($dir)) { |
537
|
|
|
@mkdir($dir, 0777, true); |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
if (!is_writable($dir)) { |
541
|
|
|
@chmod($dir, 0777); |
542
|
|
|
} |
543
|
|
|
|
544
|
|
|
if (!is_writable($dir)) { |
545
|
|
|
throw new ConfigurationException( |
546
|
|
|
"Path for output is not writable. Please, set appropriate access mode for output path." |
547
|
|
|
); |
548
|
|
|
} |
549
|
|
|
|
550
|
|
|
return $dir; |
551
|
|
|
} |
552
|
|
|
|
553
|
|
|
/** |
554
|
|
|
* Compatibility alias to `Configuration::logDir()` |
555
|
|
|
* @return string |
556
|
|
|
*/ |
557
|
|
|
public static function logDir() |
558
|
|
|
{ |
559
|
|
|
return self::outputDir(); |
560
|
|
|
} |
561
|
|
|
|
562
|
|
|
/** |
563
|
|
|
* Returns path to the root of your project. |
564
|
|
|
* Basically returns path to current `codeception.yml` loaded. |
565
|
|
|
* Use this method instead of `__DIR__`, `getcwd()` or anything else. |
566
|
|
|
* @return string |
567
|
|
|
*/ |
568
|
|
|
public static function projectDir() |
569
|
|
|
{ |
570
|
|
|
return self::$dir . DIRECTORY_SEPARATOR; |
571
|
|
|
} |
572
|
|
|
|
573
|
|
|
|
574
|
|
|
/** |
575
|
|
|
* Returns path to tests directory |
576
|
|
|
* |
577
|
|
|
* @return string |
578
|
|
|
*/ |
579
|
|
|
public static function testsDir() |
580
|
|
|
{ |
581
|
|
|
return self::$dir . DIRECTORY_SEPARATOR . self::$testsDir . DIRECTORY_SEPARATOR; |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* Return current path to `_envs` dir. |
586
|
|
|
* Use it to store environment specific configuration. |
587
|
|
|
* |
588
|
|
|
* @return string |
589
|
|
|
*/ |
590
|
|
|
public static function envsDir() |
591
|
|
|
{ |
592
|
|
|
if (!self::$envsDir) { |
593
|
|
|
return null; |
594
|
|
|
} |
595
|
|
|
return self::$dir . DIRECTORY_SEPARATOR . self::$envsDir . DIRECTORY_SEPARATOR; |
596
|
|
|
} |
597
|
|
|
|
598
|
|
|
/** |
599
|
|
|
* Is this a meta-configuration file that just points to other `codeception.yml`? |
600
|
|
|
* If so, it may have no tests by itself. |
601
|
|
|
* |
602
|
|
|
* @return bool |
603
|
|
|
*/ |
604
|
|
|
public static function isEmpty() |
605
|
|
|
{ |
606
|
|
|
return !(bool)self::$testsDir; |
607
|
|
|
} |
608
|
|
|
|
609
|
|
|
/** |
610
|
|
|
* Adds parameters to config |
611
|
|
|
* |
612
|
|
|
* @param array $config |
613
|
|
|
* @return array |
614
|
|
|
*/ |
615
|
|
|
public static function append(array $config = []) |
616
|
|
|
{ |
617
|
|
|
self::$config = self::mergeConfigs(self::$config, $config); |
618
|
|
|
|
619
|
|
|
if (isset(self::$config['paths']['output'])) { |
620
|
|
|
self::$outputDir = self::$config['paths']['output']; |
621
|
|
|
} |
622
|
|
|
if (isset(self::$config['paths']['data'])) { |
623
|
|
|
self::$dataDir = self::$config['paths']['data']; |
624
|
|
|
} |
625
|
|
|
if (isset(self::$config['paths']['support'])) { |
626
|
|
|
self::$supportDir = self::$config['paths']['support']; |
627
|
|
|
} |
628
|
|
|
if (isset(self::$config['paths']['tests'])) { |
629
|
|
|
self::$testsDir = self::$config['paths']['tests']; |
630
|
|
|
} |
631
|
|
|
|
632
|
|
|
return self::$config; |
633
|
|
|
} |
634
|
|
|
|
635
|
|
|
public static function mergeConfigs($a1, $a2) |
636
|
|
|
{ |
637
|
|
|
if (!is_array($a1)) { |
638
|
|
|
return $a2; |
639
|
|
|
} |
640
|
|
|
|
641
|
|
|
if (!is_array($a2)) { |
642
|
|
|
return $a1; |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
$res = []; |
646
|
|
|
|
647
|
|
|
// for sequential arrays |
648
|
|
|
if (isset($a1[0], $a2[0])) { |
649
|
|
|
return array_merge_recursive($a2, $a1); |
650
|
|
|
} |
651
|
|
|
|
652
|
|
|
// for associative arrays |
653
|
|
|
foreach ($a2 as $k2 => $v2) { |
654
|
|
|
if (!isset($a1[$k2])) { // if no such key |
655
|
|
|
$res[$k2] = $v2; |
656
|
|
|
unset($a1[$k2]); |
657
|
|
|
continue; |
658
|
|
|
} |
659
|
|
|
|
660
|
|
|
$res[$k2] = self::mergeConfigs($a1[$k2], $v2); |
661
|
|
|
unset($a1[$k2]); |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
foreach ($a1 as $k1 => $v1) { // only single elements here left |
665
|
|
|
$res[$k1] = $v1; |
666
|
|
|
} |
667
|
|
|
|
668
|
|
|
return $res; |
669
|
|
|
} |
670
|
|
|
|
671
|
|
|
/** |
672
|
|
|
* Loads config from *.dist.suite.yml and *.suite.yml |
673
|
|
|
* |
674
|
|
|
* @param $suite |
675
|
|
|
* @param $path |
676
|
|
|
* @param $settings |
677
|
|
|
* @return array |
678
|
|
|
*/ |
679
|
|
|
protected static function loadSuiteConfig($suite, $path, $settings) |
680
|
|
|
{ |
681
|
|
|
if (isset(self::$config['suites'][$suite])) { |
682
|
|
|
// bundled config |
683
|
|
|
return self::mergeConfigs($settings, self::$config['suites'][$suite]); |
684
|
|
|
} |
685
|
|
|
|
686
|
|
|
$suiteDir = self::$dir . DIRECTORY_SEPARATOR . $path; |
687
|
|
|
|
688
|
|
|
$suiteDistConf = self::getConfFromFile($suiteDir . DIRECTORY_SEPARATOR . "$suite.suite.dist.yml"); |
689
|
|
|
$suiteConf = self::getConfFromFile($suiteDir . DIRECTORY_SEPARATOR . "$suite.suite.yml"); |
690
|
|
|
|
691
|
|
|
// now we check the suite config file, if a extends key is defined |
692
|
|
|
if (isset($suiteConf['extends'])) { |
693
|
|
|
$presetFilePath = codecept_is_path_absolute($suiteConf['extends']) |
694
|
|
|
? $suiteConf['extends'] // If path is absolute – use it |
695
|
|
|
: realpath($suiteDir . DIRECTORY_SEPARATOR . $suiteConf['extends']); // Otherwise try to locate it in the suite dir |
696
|
|
|
|
697
|
|
|
if (file_exists($presetFilePath)) { |
698
|
|
|
$settings = self::mergeConfigs(self::getConfFromFile($presetFilePath), $settings); |
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
$settings = self::mergeConfigs($settings, $suiteDistConf); |
703
|
|
|
$settings = self::mergeConfigs($settings, $suiteConf); |
704
|
|
|
|
705
|
|
|
return $settings; |
706
|
|
|
} |
707
|
|
|
|
708
|
|
|
/** |
709
|
|
|
* Replaces wildcarded items in include array with real paths. |
710
|
|
|
* |
711
|
|
|
* @param $includes |
712
|
|
|
* @return array |
713
|
|
|
*/ |
714
|
|
|
protected static function expandWildcardedIncludes(array $includes) |
715
|
|
|
{ |
716
|
|
|
if (empty($includes)) { |
717
|
|
|
return $includes; |
718
|
|
|
} |
719
|
|
|
$expandedIncludes = []; |
720
|
|
|
foreach ($includes as $include) { |
721
|
|
|
$expandedIncludes = array_merge($expandedIncludes, self::expandWildcardsFor($include)); |
722
|
|
|
} |
723
|
|
|
return $expandedIncludes; |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
/** |
727
|
|
|
* Finds config files in given wildcarded include path. |
728
|
|
|
* Returns the expanded paths or the original if not a wildcard. |
729
|
|
|
* |
730
|
|
|
* @param $include |
731
|
|
|
* @return array |
732
|
|
|
* @throws ConfigurationException |
733
|
|
|
*/ |
734
|
|
|
protected static function expandWildcardsFor($include) |
735
|
|
|
{ |
736
|
|
|
if (1 !== preg_match('/[\?\.\*]/', $include)) { |
737
|
|
|
return [$include,]; |
738
|
|
|
} |
739
|
|
|
|
740
|
|
|
try { |
741
|
|
|
$configFiles = Finder::create()->files() |
742
|
|
|
->name('/codeception(\.dist\.yml|\.yml)/') |
743
|
|
|
->in(self::$dir . DIRECTORY_SEPARATOR . $include); |
744
|
|
|
} catch (\InvalidArgumentException $e) { |
745
|
|
|
throw new ConfigurationException( |
746
|
|
|
"Configuration file(s) could not be found in \"$include\"." |
747
|
|
|
); |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
$paths = []; |
751
|
|
|
foreach ($configFiles as $file) { |
752
|
|
|
$paths[] = codecept_relative_path($file->getPath()); |
753
|
|
|
} |
754
|
|
|
|
755
|
|
|
return $paths; |
756
|
|
|
} |
757
|
|
|
|
758
|
|
|
private static function prepareParams($settings) |
759
|
|
|
{ |
760
|
|
|
self::$params = []; |
761
|
|
|
$paramsLoader = new ParamsLoader(); |
762
|
|
|
|
763
|
|
|
foreach ($settings['params'] as $paramStorage) { |
764
|
|
|
static::$params = array_merge(self::$params, $paramsLoader->load($paramStorage)); |
765
|
|
|
} |
766
|
|
|
} |
767
|
|
|
} |
768
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.