API Update API to reflect changes to CLI interaction (#203)

This commit is contained in:
Guy Sartorelli 2024-09-26 17:17:35 +12:00 committed by GitHub
parent 4cf848954e
commit f0fe0d3607
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 136 additions and 109 deletions

View File

@ -15,7 +15,7 @@ a set of sample data and behaviour.
## Usage ## Usage
Simply running `dev/build` will take care of most sample data setup. Simply running `sake db:build` will take care of most sample data setup.
In order to use any of the optional test behaviour targeted at modules, In order to use any of the optional test behaviour targeted at modules,
install the module and remove the `_manifest_exclude` file from the relevant folder. install the module and remove the `_manifest_exclude` file from the relevant folder.
@ -26,7 +26,7 @@ For example, to test the tagfield module, remove the `frameworktest/code/tagfiel
The module creates some default pages for different CMS behaviours. The module creates some default pages for different CMS behaviours.
The CMS is intended to be perform well with a couple of thousand pages. The CMS is intended to be perform well with a couple of thousand pages.
If you want to test the CMS behaviour for a large and nested tree, If you want to test the CMS behaviour for a large and nested tree,
the module includes a simple generator task: `dev/tasks/FTPageMakerTask`. the module includes a simple generator task: `sake tasks:FTPageMakerTask`.
It will create 3^5 pages by default, so takes a while to run through. It will create 3^5 pages by default, so takes a while to run through.
## Configuring the amount of data ## Configuring the amount of data
@ -35,9 +35,9 @@ Both `FTPageMagerTask` and `FTFileMakerTask` allow the amount of generated conte
To do this, pass a comma-seprarated list of integers representing the amount of records to create at each To do this, pass a comma-seprarated list of integers representing the amount of records to create at each
depth. depth.
`$ vendor/bin/sake dev/tasks/FTPageMakerTask pageCounts=10,200,5,5` `$ vendor/bin/sake tasks:FTPageMakerTask --pageCounts=10,200,5,5`
`$ vendor/bin/sake dev/tasks/FTFileMakerTask fileCounts=5,300,55,5 folderCounts=1,5,5,5` `$ vendor/bin/sake tasks:FTFileMakerTask --fileCounts=5,300,55,5 --folderCounts=1,5,5,5`
## Guaranteed unique images ## Guaranteed unique images
@ -64,8 +64,8 @@ Usage:
``` ```
# Generate some sample files to associate with blocks # Generate some sample files to associate with blocks
sake dev/tasks/FTFileMakerTask sake tasks:FTFileMakerTask
sake dev/tasks/FTPageMakerTask withBlocks=true sake tasks:FTPageMakerTask withBlocks=true
``` ```
## Requirements ## Requirements

View File

@ -28,9 +28,9 @@ SilverStripe\FrameworkTest\Fields\NestedGridField\NonRelationalData:
extensions: extensions:
- SilverStripe\FrameworkTest\Extension\TestDataObjectExtension - SilverStripe\FrameworkTest\Extension\TestDataObjectExtension
SilverStripe\ORM\DatabaseAdmin: SilverStripe\Dev\Command\DbBuild:
extensions: extensions:
- SilverStripe\FrameworkTest\GridFieldArbitraryData\DatabaseBuildExtension - SilverStripe\FrameworkTest\GridFieldArbitraryData\DbBuildExtension
--- ---
Only: Only:
@ -38,7 +38,7 @@ Only:
--- ---
SilverStripe\TestSession\TestSessionEnvironment: SilverStripe\TestSession\TestSessionEnvironment:
extensions: extensions:
- SilverStripe\FrameworkTest\GridFieldArbitraryData\DatabaseBuildExtension - SilverStripe\FrameworkTest\GridFieldArbitraryData\DbBuildExtension
--- ---
Only: Only:

View File

@ -4,7 +4,8 @@ namespace SilverStripe\FrameworkTest\GridFieldArbitraryData;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Extension; use SilverStripe\Core\Extension;
use SilverStripe\ORM\DatabaseAdmin; use SilverStripe\Dev\Command\DbBuild;
use SilverStripe\PolyExecution\PolyOutput;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\FieldType\DBDatetime;
@ -13,87 +14,66 @@ use SilverStripe\ORM\Queries\SQLSelect;
/** /**
* Builds the table and adds default records for the ArbitraryDataModel. * Builds the table and adds default records for the ArbitraryDataModel.
*/ */
class DatabaseBuildExtension extends Extension class DbBuildExtension extends Extension
{ {
/** /**
* This extension hook is on TestSessionEnvironment, which is used by behat but not by phpunit. * This extension hook is on TestSessionEnvironment, which is used by behat but not by phpunit.
* For whatever reason, behat doesn't use dev/build, so we can't rely on the below onAfterbuild * For whatever reason, behat doesn't build the db, so we can't rely on the below onAfterbuild
* being run in that scenario. * being run in that scenario.
*/ */
protected function onAfterStartTestSession() protected function onAfterStartTestSession()
{ {
$this->buildTable(true); $output = PolyOutput::create(
Director::is_cli() ? PolyOutput::FORMAT_ANSI : PolyOutput::FORMAT_HTML,
PolyOutput::VERBOSITY_QUIET
);
$output->startList();
$this->buildTable($output);
$output->stopList();
$this->populateData(); $this->populateData();
} }
/** /**
* This extension hook is on DatabaseAdmin, after dev/build has finished building the database. * This extension hook is on DbBuild, after building the database.
*/ */
protected function onAfterBuild(bool $quiet, bool $populate, bool $testMode): void protected function onAfterBuild(PolyOutput $output, bool $populate, bool $testMode): void
{ {
if ($testMode) { if ($testMode) {
return; return;
} }
if (!$quiet) { $output->writeln('<options=bold>Creating table for frameworktest arbitrary data</>');
if (Director::is_cli()) { $output->startList();
echo "\nCREATING TABLE FOR FRAMEWORKTEST ARBITRARY DATA\n\n"; $this->buildTable($output);
} else { $output->stopList();
echo "\n<p><b>Creating table for frameworktest arbitrary data</b></p><ul>\n\n";
}
}
$this->buildTable($quiet);
if (!$quiet && !Director::is_cli()) {
echo '</ul>';
}
if ($populate) { if ($populate) {
if (!$quiet) { $output->writeln('<options=bold>Creating database records arbitrary data</>');
if (Director::is_cli()) { $output->startList();
echo "\nCREATING DATABASE RECORDS FOR FRAMEWORKTEST ARBITRARY DATA\n\n";
} else {
echo "\n<p><b>Creating database records arbitrary data</b></p><ul>\n\n";
}
}
$this->populateData(); $this->populateData();
$output->stopList();
if (!$quiet && !Director::is_cli()) {
echo '</ul>';
}
}
if (!$quiet) {
echo (Director::is_cli()) ? "\n Frameworktest database build completed!\n\n" : '<p>Frameworktest database build completed!</p>';
} }
$output->writeln(['<options=bold>Frameworktest database build completed!</>', '']);
} }
private function buildTable(bool $quiet): void private function buildTable(PolyOutput $output): void
{ {
$tableName = ArbitraryDataModel::TABLE_NAME; $tableName = ArbitraryDataModel::TABLE_NAME;
// Log data // Log data
if (!$quiet) { $showRecordCounts = DbBuild::config()->get('show_record_counts');
$showRecordCounts = DatabaseAdmin::config()->get('show_record_counts'); if ($showRecordCounts && DB::get_schema()->hasTable($tableName)) {
if ($showRecordCounts && DB::get_schema()->hasTable($tableName)) { try {
try { $count = SQLSelect::create()->setFrom($tableName)->count();
$count = SQLSelect::create()->setFrom($tableName)->count(); $countSuffix = " ($count records)";
$countSuffix = " ($count records)"; } catch (\Exception $e) {
} catch (\Exception $e) { $countSuffix = ' (error getting record count)';
$countSuffix = ' (error getting record count)';
}
} else {
$countSuffix = "";
}
if (Director::is_cli()) {
echo " * $tableName$countSuffix\n";
} else {
echo "<li>$tableName$countSuffix</li>\n";
} }
} else {
$countSuffix = "";
} }
// We're adding one list item, but we need to do it this way for consistency
// with the rest of the db build output
$output->writeListItem($tableName . $countSuffix);
// Get field schema // Get field schema
$fields = [ $fields = [

View File

@ -11,6 +11,10 @@ use SilverStripe\Security\Member;
use SilverStripe\Security\Security; use SilverStripe\Security\Security;
use SilverStripe\Core\Path; use SilverStripe\Core\Path;
use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Core\Manifest\ModuleResourceLoader;
use SilverStripe\PolyExecution\PolyOutput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
/** /**
* Creates sample folder and file structure, useful to test performance, * Creates sample folder and file structure, useful to test performance,
@ -24,10 +28,6 @@ use SilverStripe\Core\Manifest\ModuleResourceLoader;
* recursively delete any generated ones through the following bash command in `assets/`: * recursively delete any generated ones through the following bash command in `assets/`:
* `find . -name '*Resampled*' -print0 | xargs -0 rm` * `find . -name '*Resampled*' -print0 | xargs -0 rm`
* *
* Parameters:
* - reset=1: Optionally truncate ALL files and folders in the database, plus delete
* the entire `assets/` directory.
*
* *
* Configuration * Configuration
* *
@ -58,7 +58,7 @@ use SilverStripe\Core\Manifest\ModuleResourceLoader;
* - 0 * - 0
* *
* Flush and run: * Flush and run:
* /dev/tasks/FTFileMakerTask?flush&reset=1 * sake tasks:FTFileMakerTask --flush --reset
* *
* @todo Automatically retrieve file listing from S3 * @todo Automatically retrieve file listing from S3
* @todo Handle HTTP errors from S3 * @todo Handle HTTP errors from S3
@ -180,8 +180,6 @@ class FTFileMakerTask extends BuildTask
'video.m4v' => 'SilverStripe\Assets\File', 'video.m4v' => 'SilverStripe\Assets\File',
]; ];
protected $lineBreak = "\n<br>";
/** @var Member */ /** @var Member */
protected $anonymousMember = null; protected $anonymousMember = null;
@ -197,7 +195,7 @@ class FTFileMakerTask extends BuildTask
*/ */
protected $folderCounts = []; protected $folderCounts = [];
public function run($request) protected function execute(InputInterface $input, PolyOutput $output): int
{ {
set_time_limit(0); set_time_limit(0);
@ -213,15 +211,11 @@ class FTFileMakerTask extends BuildTask
} }
Security::setCurrentUser($this->anonymousMember); Security::setCurrentUser($this->anonymousMember);
if (php_sapi_name() == "cli") { if ($input->getOption('reset')) {
$this->lineBreak = "\n"; $this->reset($output);
} }
if ($request->getVar('reset')) { $fileCounts = $input->getOption('fileCounts');
$this->reset();
}
$fileCounts = $request->getVar('fileCounts');
if ($fileCounts) { if ($fileCounts) {
$counts = explode(',', $fileCounts ?? ''); $counts = explode(',', $fileCounts ?? '');
$this->fileCounts = array_map(function ($int) { $this->fileCounts = array_map(function ($int) {
@ -231,7 +225,7 @@ class FTFileMakerTask extends BuildTask
$this->fileCounts = FTFileMakerTask::config()->get('fileCountByDepth'); $this->fileCounts = FTFileMakerTask::config()->get('fileCountByDepth');
} }
$folderCounts = $request->getVar('folderCounts'); $folderCounts = $input->getOption('folderCounts');
if ($folderCounts) { if ($folderCounts) {
$counts = explode(',', $folderCounts ?? ''); $counts = explode(',', $folderCounts ?? '');
$this->folderCounts = array_map(function ($int) { $this->folderCounts = array_map(function ($int) {
@ -241,26 +235,27 @@ class FTFileMakerTask extends BuildTask
$this->folderCounts = FTFileMakerTask::config()->get('folderCountByDepth'); $this->folderCounts = FTFileMakerTask::config()->get('folderCountByDepth');
} }
echo "Downloading fixtures" . $this->lineBreak; $output->writeln('Downloading fixtures');
$fixtureFilePaths = $this->downloadFixtureFiles(); $fixtureFilePaths = $this->downloadFixtureFiles($output);
if (!FTFileMakerTask::config()->get('documentsOnly')) { if (!FTFileMakerTask::config()->get('documentsOnly')) {
echo "Generate thumbnails" . $this->lineBreak; $output->writeln('Generate thumbnails');
$this->generateThumbnails($fixtureFilePaths); $this->generateThumbnails($fixtureFilePaths);
} }
echo "Generate files" . $this->lineBreak; $output->writeln('Generate files');
$this->generateFiles($fixtureFilePaths); $this->generateFiles($fixtureFilePaths);
if (!FTFileMakerTask::config()->get('doPutProtectedFilesInPublicStore')) { if (!FTFileMakerTask::config()->get('doPutProtectedFilesInPublicStore')) {
echo "Incorrectly putting protected files into public asset store on purpose" . $this->lineBreak; $output->writeln('Incorrectly putting protected files into public asset store on purpose');
$this->putProtectedFilesInPublicAssetStore(); $this->putProtectedFilesInPublicAssetStore();
} }
return Command::SUCCESS;
} }
protected function reset() protected function reset(PolyOutput $output)
{ {
echo "Resetting assets" . $this->lineBreak; $output->writeln('Resetting assets');
DB::query('TRUNCATE "File"'); DB::query('TRUNCATE "File"');
DB::query('TRUNCATE "File_Live"'); DB::query('TRUNCATE "File_Live"');
@ -271,7 +266,7 @@ class FTFileMakerTask extends BuildTask
} }
} }
protected function downloadFixtureFiles() protected function downloadFixtureFiles(PolyOutput $output)
{ {
$client = new Client(['base_uri' => $this->fixtureFileBaseUrl]); $client = new Client(['base_uri' => $this->fixtureFileBaseUrl]);
@ -293,7 +288,7 @@ class FTFileMakerTask extends BuildTask
$promises[$filename] = $client->getAsync($filename, [ $promises[$filename] = $client->getAsync($filename, [
'sink' => $path 'sink' => $path
]); ]);
echo "Downloading $url" . $this->lineBreak; $output->writeln("Downloading $url");
} }
} }
@ -497,4 +492,28 @@ class FTFileMakerTask extends BuildTask
return $targetPath; return $targetPath;
} }
public function getOptions(): array
{
return [
new InputOption(
'reset',
null,
InputOption::VALUE_NONE,
'Optionally truncate ALL files and folders in the database,'
. ' plus delete the entire `assets/` directory',
),
new InputOption(
'fileCounts',
null,
InputOption::VALUE_REQUIRED,
'Comma separated string'
),
new InputOption(
'folderCounts',
null,
InputOption::VALUE_REQUIRED,
'Comma separated string'
),
];
}
} }

View File

@ -8,7 +8,10 @@ use SilverStripe\ElementalBannerBlock\Block\BannerBlock;
use SilverStripe\ElementalFileBlock\Block\FileBlock; use SilverStripe\ElementalFileBlock\Block\FileBlock;
use SilverStripe\CMS\Model\SiteTree; use SilverStripe\CMS\Model\SiteTree;
use DNADesign\Elemental\Extensions\ElementalPageExtension; use DNADesign\Elemental\Extensions\ElementalPageExtension;
use DNADesign\Elemental\Models\BaseElement; use SilverStripe\PolyExecution\PolyOutput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
/** /**
* Creates sample page structure, useful to test tree performance, * Creates sample page structure, useful to test tree performance,
@ -48,16 +51,16 @@ class FTPageMakerTask extends BuildTask
'SilverStripe\ElementalFileBlock\Block\FileBlock' => [FTPageMakerTask::class, 'generateFileBlock'], 'SilverStripe\ElementalFileBlock\Block\FileBlock' => [FTPageMakerTask::class, 'generateFileBlock'],
]; ];
public function run($request) protected function execute(InputInterface $input, PolyOutput $output): int
{ {
// Optionally add blocks // Optionally add blocks
$withBlocks = (bool)$request->getVar('withBlocks'); $withBlocks = (bool)$input->getOption('withBlocks');
if ($withBlocks && !class_exists('DNADesign\Elemental\Models\BaseElement')) { if ($withBlocks && !class_exists('DNADesign\Elemental\Models\BaseElement')) {
throw new \LogicException('withBlocks requested, but BaseElement class not found'); throw new \LogicException('withBlocks requested, but BaseElement class not found');
} }
// Allow pageCountByDepth to be passed as comma-separated value, e.g. pageCounts=5,100,1,1 // Allow pageCountByDepth to be passed as comma-separated value, e.g. pageCounts=5,100,1,1
$pageCounts = $request->getVar('pageCounts'); $pageCounts = $input->getOption('pageCounts');
if ($pageCounts) { if ($pageCounts) {
$counts = explode(',', $pageCounts ?? ''); $counts = explode(',', $pageCounts ?? '');
$this->pageCountByDepth = array_map(function ($int) { $this->pageCountByDepth = array_map(function ($int) {
@ -65,10 +68,11 @@ class FTPageMakerTask extends BuildTask
}, $counts ?? []); }, $counts ?? []);
} }
$this->generatePages(0, "", 0, $withBlocks); $this->generatePages($output, 0, "", 0, $withBlocks);
return Command::SUCCESS;
} }
protected function generatePages($depth = 0, $prefix = "", $parentID = 0, $withBlocks = false) protected function generatePages(PolyOutput $output, $depth = 0, $prefix = "", $parentID = 0, $withBlocks = false)
{ {
$maxDepth = count($this->pageCountByDepth ?? []); $maxDepth = count($this->pageCountByDepth ?? []);
$pageCount = $this->pageCountByDepth[$depth]; $pageCount = $this->pageCountByDepth[$depth];
@ -84,10 +88,10 @@ class FTPageMakerTask extends BuildTask
$page->write(); $page->write();
$page->copyVersionToStage('Stage', 'Live'); $page->copyVersionToStage('Stage', 'Live');
echo "Created '$page->Title' ($page->ClassName)\n"; $output->writeln("Created '$page->Title' ($page->ClassName)");
if ($withBlocks) { if ($withBlocks) {
$this->generateBlocksForPage($page); $this->generateBlocksForPage($output, $page);
} }
$pageID = $page->ID; $pageID = $page->ID;
@ -95,12 +99,12 @@ class FTPageMakerTask extends BuildTask
unset($page); unset($page);
if ($depth < $maxDepth-1) { if ($depth < $maxDepth-1) {
$this->generatePages($depth+1, $fullPrefix, $pageID, $withBlocks); $this->generatePages($output, $depth+1, $fullPrefix, $pageID, $withBlocks);
} }
} }
} }
protected function generateBlocksForPage(Page $page) protected function generateBlocksForPage(PolyOutput $output, Page $page)
{ {
$classes = array_filter($this->config()->get('block_generators') ?? [], function ($callable, $class) { $classes = array_filter($this->config()->get('block_generators') ?? [], function ($callable, $class) {
return class_exists($class ?? ''); return class_exists($class ?? '');
@ -121,7 +125,7 @@ class FTPageMakerTask extends BuildTask
$block->publishRecursive(); $block->publishRecursive();
} }
echo sprintf(" Added '%s' block #%d to page #%d\n", $class, $block->ID, $page->ID); $output->writeln(sprintf(" Added '%s' block #%d to page #%d", $class, $block->ID, $page->ID));
} }
} }
@ -193,4 +197,21 @@ class FTPageMakerTask extends BuildTask
return $block; return $block;
} }
public function getOptions(): array
{
return [
new InputOption(
'withBlocks',
null,
InputOption::VALUE_NONE,
'Include elemental blocks on the page',
),
new InputOption(
'pageCounts',
null,
InputOption::VALUE_REQUIRED,
'Comma separated string'
),
];
}
} }

View File

@ -2,10 +2,13 @@
use SilverStripe\Dev\BuildTask; use SilverStripe\Dev\BuildTask;
use Faker\Factory; use Faker\Factory;
use SilverStripe\Control\HTTPRequest;
use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder; use Symfony\Component\Finder\Finder;
use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Core\Manifest\ModuleLoader;
use SilverStripe\PolyExecution\PolyOutput;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Filesystem\Exception\IOException; use Symfony\Component\Filesystem\Exception\IOException;
class FTPageTypeCreatorTask extends BuildTask class FTPageTypeCreatorTask extends BuildTask
@ -36,12 +39,9 @@ class FTPageTypeCreatorTask extends BuildTask
$this->finder = new Finder(); $this->finder = new Finder();
} }
/** protected function execute(InputInterface $input, PolyOutput $output): int
* @param HTTPRequest $request
*/
public function run($request)
{ {
$count = $request->getVar('count') ?: 20; $count = $input->getOption('count');
$module = ModuleLoader::getModule('silverstripe/frameworktest'); $module = ModuleLoader::getModule('silverstripe/frameworktest');
$testPageDir = $module->getPath() . '/code/test-pages'; $testPageDir = $module->getPath() . '/code/test-pages';
if (!$this->fs->exists($testPageDir)) { if (!$this->fs->exists($testPageDir)) {
@ -66,11 +66,19 @@ class FTPageTypeCreatorTask extends BuildTask
$this->fs->dumpFile($filePath, $code); $this->fs->dumpFile($filePath, $code);
$created++; $created++;
} catch (IOException $e) { } catch (IOException $e) {
echo "Could not write to file $filePath. Got error: {$e->getMessage()}\n"; $output->writeln("Could not write to file $filePath. Got error: {$e->getMessage()}");
die(); die();
} }
echo "Created page type $className\n"; $output->writeln("Created page type $className");
} }
return Command::SUCCESS;
}
public function getOptions(): array
{
return [
new InputOption('count', null, InputOption::VALUE_REQUIRED, 'Number of page types to create', 20),
];
} }
private function getExistingClassNames($dir) private function getExistingClassNames($dir)
@ -107,5 +115,4 @@ class $className extends Page implements TestPageInterface
PHP; PHP;
return $code; return $code;
} }
} }