Skip to content

Commit f9bf153

Browse files
authored
SprinkleManager & Bakery Test improvements. (#928)
1 parent 3795c1e commit f9bf153

14 files changed

Lines changed: 596 additions & 48 deletions

File tree

CHANGELOG.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,28 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8-
## 4.2.0
8+
## 4.2.0-beta.2
9+
10+
### Added
11+
- Added `sprinkle:list` bakery command
12+
- Changed the sprinkle list in the bakery `debug` command to use the new `sprinkle:list` table
13+
14+
### Fix
15+
- Fix for `Test` Bakery command
16+
- Added `coverage-format` and `coverage-path` options to `test` Bakery command
17+
- Sprinkle Testscope is now case insensitive
18+
- **Class testscope now relative to `/` instead of `/UserFrosting/Sprinkle/` for more intuitive usage and to enable testing of non sprinkle tests**
19+
- Detect and use the sprinkle `phpunit.xml` config when testing a specific sprinkle
20+
- SprinkleManager Improvements :
21+
- Added public `getSprinklePath` method to get path to the sprinkle directory
22+
- Added public `getSprinkleClassNamespace` method to get sprinkle base namespace
23+
- Added public `getSprinkle` method. Returns the sprinkle name as formatted in `sprinkles.json` file, independent of the case of the search argument.
24+
- Public `isAvailable` method now case insensitive.
25+
- Added public `getSprinklesPath` & `setSprinklesPath` to return or set the path to the sprinkle dir (`app/sprinkles/`)
26+
- Added `JsonException` if `sprinkles.json` doesn't contain valid json.
27+
- Added specific tests for sprinkleManager with 100% test coverage
28+
29+
## 4.2.0-beta.1
930

1031
### Changed Requirements
1132
- Changed minimum Node.js version to **v10.12.0**

app/sprinkles/core/src/Bakery/DebugCommand.php

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
5454
$this->checkPhpVersion();
5555
$this->checkNodeVersion();
5656
$this->checkNpmVersion();
57-
$this->listSprinkles();
57+
$this->listSprinkles($input, $output);
5858
$this->showConfig();
5959
$this->checkDatabase();
6060

@@ -83,23 +83,27 @@ protected function checkPhpVersion()
8383
/**
8484
* List all sprinkles defined in the Sprinkles schema file,
8585
* making sure this file exist at the same time
86+
*
87+
* @param InputInterface $input
88+
* @param OutputInterface $output
8689
*/
87-
protected function listSprinkles()
90+
protected function listSprinkles(InputInterface $input, OutputInterface $output)
8891
{
8992
// Check for Sprinkles schema file
9093
$path = \UserFrosting\SPRINKLES_SCHEMA_FILE;
91-
$sprinklesFile = @file_get_contents($path);
92-
if ($sprinklesFile === false) {
94+
if (@file_exists($path) === false) {
9395
$this->io->error("The file `$path` not found.");
9496
}
9597

9698
// List installed sprinkles
97-
$sprinkles = json_decode($sprinklesFile)->base;
98-
$this->io->section('Loaded sprinkles');
99-
$this->io->listing($sprinkles);
99+
$command = $this->getApplication()->find('sprinkle:list');
100+
$command->run($input, $output);
101+
102+
/** @var \UserFrosting\System\Sprinkle\SprinkleManager $sprinkleManager */
103+
$sprinkleManager = $this->ci->sprinkleManager;
100104

101105
// Throw fatal error if the `core` sprinkle is missing
102-
if (!in_array('core', $sprinkles)) {
106+
if (!$sprinkleManager->isAvailable('core')) {
103107
$this->io->error("The `core` sprinkle is missing from the 'sprinkles.json' file.");
104108
exit(1);
105109
}
@@ -111,7 +115,7 @@ protected function listSprinkles()
111115
*/
112116
protected function checkDatabase()
113117
{
114-
$this->io->section('Testing database connection...');
118+
$this->io->title('Testing database connection...');
115119

116120
try {
117121
$this->testDB();
@@ -134,7 +138,7 @@ protected function showConfig()
134138
$config = $this->ci->config;
135139

136140
// Display database info
137-
$this->io->section('Database config');
141+
$this->io->title('Database config');
138142
$this->io->writeln([
139143
'DRIVER : ' . $config['db.default.driver'],
140144
'HOST : ' . $config['db.default.host'],

app/sprinkles/core/src/Bakery/RouteListCommand.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ protected function execute(InputInterface $input, OutputInterface $output)
5454
$this->io->title('Registered Routes');
5555

5656
// Get routes list
57-
$this->routes = $this->ci->router->getRoutes();
57+
$routes = $this->ci->router->getRoutes();
5858

5959
// If not route, don't go further
60-
if (count($this->routes) === 0) {
60+
if (count($routes) === 0) {
6161
return $this->io->error("Your application doesn't have any routes.");
6262
}
6363

6464
// Compile the routes into a displayable format
65-
$routes = collect($this->routes)->map(function ($route) use ($input) {
65+
$routes = collect($routes)->map(function ($route) use ($input) {
6666
return $this->getRouteInformation($route, $input);
6767
})->all();
6868

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?php
2+
/**
3+
* UserFrosting (http://www.userfrosting.com)
4+
*
5+
* @link https://github.com/userfrosting/UserFrosting
6+
* @copyright Copyright (c) 2019 Alexander Weissman
7+
* @license https://github.com/userfrosting/UserFrosting/blob/master/LICENSE.md (MIT License)
8+
*/
9+
10+
namespace UserFrosting\Sprinkle\Core\Bakery;
11+
12+
use Symfony\Component\Console\Input\InputInterface;
13+
use Symfony\Component\Console\Output\OutputInterface;
14+
use UserFrosting\System\Bakery\BaseCommand;
15+
16+
/**
17+
* Sprinkle:list CLI tool.
18+
*/
19+
class SprinkleListCommand extends BaseCommand
20+
{
21+
/**
22+
* @var array The table header
23+
*/
24+
protected $headers = ['Sprinkle', 'Calculated Namespace', 'Calculated Path'];
25+
26+
/**
27+
* {@inheritdoc}
28+
*/
29+
protected function configure()
30+
{
31+
$this->setName('sprinkle:list')
32+
->setDescription('List all available sprinkles and their params');
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
protected function execute(InputInterface $input, OutputInterface $output)
39+
{
40+
$this->io->title('Loaded Sprinkles');
41+
42+
/** @var \UserFrosting\System\Sprinkle\SprinkleManager $sprinkleManager */
43+
$sprinkleManager = $this->ci->sprinkleManager;
44+
45+
// Get sprinkle list
46+
$sprinkles = $sprinkleManager->getSprinkleNames();
47+
48+
// Compile the routes into a displayable format
49+
$sprinklesTable = collect($sprinkles)->map(function ($sprinkle) use ($sprinkleManager) {
50+
return [
51+
'sprinkle' => $sprinkle,
52+
'namespace' => $sprinkleManager->getSprinkleClassNamespace($sprinkle),
53+
'path' => $sprinkleManager->getSprinklePath($sprinkle),
54+
];
55+
})->all();
56+
57+
// Display table
58+
$this->io->table($this->headers, $sprinklesTable);
59+
}
60+
}

app/sprinkles/core/src/Bakery/Test.php

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,23 @@ class Test extends BaseCommand
2929
*/
3030
protected $buildPath;
3131

32+
/**
33+
* @var string
34+
*/
35+
protected $slashes = '\\';
36+
3237
/**
3338
* {@inheritdoc}
3439
*/
3540
protected function configure()
3641
{
3742
$this->setName('test')
38-
->addOption('coverage', 'c', InputOption::VALUE_NONE, 'Generate code coverage report in HTML format. Will be saved in _meta/coverage')
39-
->addArgument('testscope', InputArgument::OPTIONAL, "Test Scope can either be a sprinkle name or a class formated as 'SprinkleName\Tests\TestClass` or 'SprinkleName\Tests\TestClass::method` (Optional)")
43+
->addOption('coverage', 'c', InputOption::VALUE_NONE, 'Enable code coverage report.')
44+
->addOption('coverage-format', null, InputOption::VALUE_REQUIRED, 'Select test coverage format. Choose from html, clover, crap4j, php, text, xml, etc. Default to HTML.')
45+
->addOption('coverage-path', null, InputOption::VALUE_REQUIRED, 'Code coverage report saving location. Default to `_meta/coverage`.')
46+
->addArgument('testscope', InputArgument::OPTIONAL, 'Test Scope can either be a sprinkle name or a test class (optional)')
4047
->setDescription('Runs automated tests')
41-
->setHelp("Run PHP unit tests. Tests from a specific sprinkle can optionally be run using the 'testscope' argument (`php bakery test SprinkleName`). A specific test class can also be be run using the testscope argument (`php bakery test 'SprinkleName\Tests\TestClass'`), as a specific test method (`php bakery test 'SprinkleName\Tests\TestClass::method'`).");
48+
->setHelp("Run PHP unit tests. Tests from a specific sprinkle can optionally be run using the 'testscope' argument (`php bakery test SprinkleName`). A specific test class can also be be run using the testscope argument (`php bakery test 'UserFrosting\Sprinkle\SprinkleName\Tests\TestClass'`), as a specific test method (`php bakery test 'UserFrosting\Sprinkle\SprinkleName\Tests\TestClass::method'`).");
4249
}
4350

4451
/**
@@ -54,26 +61,93 @@ protected function execute(InputInterface $input, OutputInterface $output)
5461
$command .= ' -v';
5562
}
5663

64+
// Process test scope
5765
$testscope = $input->getArgument('testscope');
5866
if ($testscope) {
59-
$slashes = '\\\\';
60-
if (strpos($testscope, '\\') !== false) {
61-
$this->io->note("Executing Specified Test Scope : $testscope");
62-
$testscope = str_replace('\\', $slashes, $testscope);
63-
$command .= " --filter='UserFrosting" . $slashes . 'Sprinkle' . $slashes . $testscope . "'";
67+
if (strpos($testscope, $this->slashes) !== false) {
68+
$command .= $this->parseTestScope($testscope);
6469
} else {
65-
$this->io->note("Executing all tests in Sprinkle '".Str::studly($testscope)."'");
66-
$command .= " --filter='UserFrosting" . $slashes . 'Sprinkle' . $slashes . Str::studly($testscope) . $slashes . 'Tests' . $slashes . "' ";
70+
$command .= $this->parseSprinkleTestScope($testscope);
6771
}
6872
}
6973

7074
// Add coverage report
71-
if ($input->getOption('coverage')) {
72-
$command .= ' --coverage-html _meta/coverage';
75+
if ($input->getOption('coverage') || $input->getOption('coverage-format') || $input->getOption('coverage-path')) {
76+
$format = ($input->getOption('coverage-format')) ?: 'html';
77+
$path = ($input->getOption('coverage-path')) ?: '_meta/coverage';
78+
79+
switch ($format) {
80+
case 'clover':
81+
case 'xml':
82+
case 'crap4j':
83+
$path = $path . '/coverage.xml';
84+
break;
85+
case 'php':
86+
$path = $path . '/coverage.php';
87+
break;
88+
case 'text':
89+
$path = '';
90+
break;
91+
case 'html':
92+
default:
93+
$file = '';
94+
break;
95+
}
96+
97+
$command .= " --coverage-$format $path";
7398
}
7499

75100
// Execute
76101
$this->io->writeln("> <comment>$command</comment>");
77102
passthru($command);
78103
}
104+
105+
/**
106+
* Return the sprinkle test class
107+
*
108+
* @param string $testscope Testscope received from command line
109+
* @return string
110+
*/
111+
protected function parseSprinkleTestScope($testscope)
112+
{
113+
/** @var \UserFrosting\System\Sprinkle\SprinkleManager $sprinkleManager */
114+
$sprinkleManager = $this->ci->sprinkleManager;
115+
116+
// Get the Sprinkle name from the SprinkleManager, as we need the correct case
117+
$sprinkle = $sprinkleManager->getSprinkle($testscope);
118+
119+
// Make sure sprinkle exist
120+
if (!$sprinkle) {
121+
$this->io->error("Sprinkle $testscope not found");
122+
exit(1);
123+
}
124+
125+
$sprinkleName = Str::studly($sprinkle);
126+
$this->io->note("Executing all tests for Sprinkle '$sprinkleName'");
127+
128+
// Check if sprinkle has phpunit.xml file
129+
$phpunitConfig = $sprinkleManager->getSprinklePath($sprinkle) . \UserFrosting\DS . 'phpunit.xml';
130+
if (file_exists($phpunitConfig)) {
131+
return " -c $phpunitConfig ";
132+
}
133+
134+
// Add command part
135+
$testClass = $sprinkleManager->getSprinkleClassNamespace($sprinkleName) . "\Tests";
136+
$testClass = str_replace($this->slashes, $this->slashes . $this->slashes, $testClass);
137+
138+
return " --filter='$testClass' ";
139+
}
140+
141+
/**
142+
* Parse testscope for
143+
* @param string $testscope
144+
* @return string string to append to command
145+
*/
146+
protected function parseTestScope($testscope)
147+
{
148+
$this->io->note("Executing Specified Test Scope : $testscope");
149+
$testscope = str_replace($this->slashes, $this->slashes . $this->slashes, $testscope);
150+
151+
return " --filter='$testscope'";
152+
}
79153
}

0 commit comments

Comments
 (0)