Skip to content

Commit

Permalink
Merge pull request #71 from kitsunet/task/add-framework-testing-boile…
Browse files Browse the repository at this point in the history
…rplate

TASK: Benchmark basics
  • Loading branch information
kitsunet committed Jun 10, 2024
2 parents a3e4777 + 33c28a4 commit 1b0d76b
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 16 deletions.
67 changes: 67 additions & 0 deletions Classes/PhpBench/FrameworkEnabledBenchmark.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/*
* (c) Contributors of the Neos Project - www.neos.io
* Please see the LICENSE file which was distributed with this source code.
*/

declare(strict_types=1);

namespace Neos\BuildEssentials\PhpBench;

use Neos\BuildEssentials\TestableFramework;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Testing\RequestHandler\RuntimeSequenceInvokingRequestHandler;

/**
* Tools for https://github.com/phpbench/phpbench
*/
class FrameworkEnabledBenchmark
{
protected Bootstrap $flowBootstrap;

/**
* Use with phpbench BeforeClassMethods annotation to prepare persistence schema
*/
public static function enablePersistence(): void
{
$_SERVER['FLOW_ROOTPATH'] = TestableFramework::calculateRootPath();
$bootstrap = TestableFramework::buildBootstrapWithPreselectedRequestHandler(TestableFramework::getApplicationContext(), RuntimeSequenceInvokingRequestHandler::class);
$bootstrap->run();
TestableFramework::enablePersistence($bootstrap);
}

/**
* Use with phpbench AfterClassMethods annotation to cleanup persistence schema
*/
public static function cleanUpPersistence(): void
{
$_SERVER['FLOW_ROOTPATH'] = TestableFramework::calculateRootPath();
$bootstrap = TestableFramework::buildBootstrapWithPreselectedRequestHandler(TestableFramework::getApplicationContext(), RuntimeSequenceInvokingRequestHandler::class);
$bootstrap->run();
TestableFramework::cleanUpPersistence($bootstrap);
}

/**
* Makes $this->flowBootstrap available with a running request handler that will have booted up the framework in runtime.
* This can be used for any test that requires Flow framework features like the ObjectManager, so that
* the whole booting process will be done before the benchmark measurement.
* Use with phpbench BeforeMethods annotation
*
*/
public function bootstrapWithTestRequestHandler(): void
{
$this->withRootPath();
$this->flowBootstrap = TestableFramework::buildBootstrapWithPreselectedRequestHandler(TestableFramework::getApplicationContext(), RuntimeSequenceInvokingRequestHandler::class);
$this->flowBootstrap->run();
}

/**
* Writes the FLOW_ROOTPATH global server variable, so it is available globally when starting the framework
* Use with phpbench BeforeMethods annotation
*/
public function withRootPath(): void
{
$_SERVER['FLOW_ROOTPATH'] = TestableFramework::calculateRootPath();
}
}
129 changes: 129 additions & 0 deletions Classes/TestableFramework.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<?php

/*
* (c) Contributors of the Neos Project - www.neos.io
* Please see the LICENSE file which was distributed with this source code.
*/

declare(strict_types=1);

namespace Neos\BuildEssentials;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Configuration\ConfigurationManager;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Core\RequestHandlerInterface;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Utility\Files;

/**
* Low level operations for use in testing, all to be used in static calls.
*
* @Flow\Proxy(false)
*/
abstract readonly class TestableFramework
{
/**
* Calculates a FLOW_ROOTPATH
* @return string
*/
public static function calculateRootPath(): string
{
$flowRootPathFromEnvironment = trim(getenv('FLOW_ROOTPATH') ?: '', '"\' ');
return $flowRootPathFromEnvironment ?: dirname(__DIR__, 3) . '/';
}

public static function getApplicationContext(): string
{
$flowContextFromEnvironment = trim(getenv('FLOW_CONTEXT') ?: '', '"\' ');
return $flowContextFromEnvironment ?: 'Testing';
}

/**
* Build a Flow Bootstrap with an instance of the given class implementing RequestHandlerInterface
*
* @param string $context
* @param class-string<RequestHandlerInterface> $requestHandlerClassname
* @return Bootstrap
*/
public static function buildBootstrapWithPreselectedRequestHandler(string $context, string $requestHandlerClassname): Bootstrap
{
$flowBootstrap = new Bootstrap($context);
$requestHandlerInstance = new $requestHandlerClassname($flowBootstrap);
assert($requestHandlerInstance instanceof RequestHandlerInterface);

$flowBootstrap->registerRequestHandler($requestHandlerInstance);
$flowBootstrap->setPreselectedRequestHandlerClassName($requestHandlerClassname);
return $flowBootstrap;
}

/**
* Creates an empty table structure according to the active ORM schemas
*
* @param Bootstrap $bootstrap
* @return void
* @throws \Doctrine\ORM\Tools\ToolsException
* @throws \Neos\Flow\Exception
*/
public static function enablePersistence(Bootstrap $bootstrap): void
{
$persistenceMangager = $bootstrap->getObjectManager()->get(PersistenceManagerInterface::class);
if (is_callable([$persistenceMangager, 'compile'])) {
$result = $persistenceMangager->compile();
if ($result === false) {
exit('Could not setup persistence for benchmark');
}
}
}

/**
* Drops persistence tables after a test
*
* @param Bootstrap $bootstrap
* @return void
* @throws \Neos\Flow\Exception
*/
public static function cleanUpPersistence(Bootstrap $bootstrap): void
{
$persistenceManager = $bootstrap->getObjectManager()->get(PersistenceManagerInterface::class);
// Explicitly call persistAll() so that the "allObjectsPersisted" signal is sent even if persistAll()
// has not been called during a test. This makes sure that for example certain repositories can clear
// their internal registry in order to avoid side effects in the following test run.
// Wrap in try/catch to suppress errors after the actual test is run (e.g. validation)
try {
$persistenceManager->persistAll();
} catch (\Exception $exception) {
}

if (is_callable([$persistenceManager, 'tearDown'])) {
$persistenceManager->tearDown();
}
}

/**
* Cleans up the directory for storing persistent resources during testing
*
* @return void
* @throws \Exception
*/
public static function cleanupPersistentResourcesDirectory(Bootstrap $bootstrap): void
{
$settings = $bootstrap->getObjectManager()->get(ConfigurationManager::class)->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_SETTINGS);
foreach ($settings['Neos']['Flow']['resource']['storages'] as $storageName => $storageSettings) {
if (!isset($storageSettings['storageOptions']['path'])) {
continue;
}

$resourcesStoragePath = $storageSettings['storageOptions']['path'];
if (!str_contains($resourcesStoragePath, FLOW_PATH_DATA)) {
throw new \Exception(sprintf('The storage path for persistent resources for the Testing context is "%s" for the "%s" storage, but it must point to a directory below "%s". Please check the Flow settings for the Testing context.', $resourcesStoragePath, $storageName, FLOW_PATH_DATA), 1382018388);
}
if (!str_contains($resourcesStoragePath, '/Test/')) {
throw new \Exception(sprintf('The storage path for persistent resources for the Testing context is "%s" for the "%s" storage, but it must contain "/Test/". Please check the Flow settings for the Testing context.', $resourcesStoragePath, $storageName), 1382018388);
}
if (file_exists($resourcesStoragePath)) {
Files::removeDirectoryRecursively($resourcesStoragePath);
}
}
}
}
30 changes: 14 additions & 16 deletions PhpUnit/FunctionalTestBootstrap.php
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
<?php
namespace Neos\Flow\Build;

/*
* This file is part of the Neos Flow build system.
*
* (c) Contributors of the Neos Project - www.neos.io
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
* Please see the LICENSE file which was distributed with this source code.
*/

$context = isset($_SERVER['FLOW_CONTEXT']) ? $_SERVER['FLOW_CONTEXT'] : 'Testing';
declare(strict_types=1);

namespace Neos\Flow\Build;

use Neos\BuildEssentials\TestableFramework;
use Neos\Flow\Core\Bootstrap;
use Neos\Flow\Testing\RequestHandler\RuntimeSequenceHttpRequestHandler;

$context = TestableFramework::getApplicationContext();

if (preg_match('/^(?:Testing|Testing\/.+)$/', $context) !== 1) {
die(sprintf('The context "%s" is not allowed. Only "Testing" context or one of its subcontexts "Testing/*" is allowed.', $context));
}

$_SERVER['FLOW_ROOTPATH'] = dirname(__FILE__) . '/../../../';

if (DIRECTORY_SEPARATOR === '/') {
// Fixes an issue with the autoloader, see FLOW-183
shell_exec('cd ' . escapeshellarg($_SERVER['FLOW_ROOTPATH']) . ' && FLOW_CONTEXT=' . escapeshellarg($context) . ' ./flow neos.flow:cache:warmup');
}
$_SERVER['FLOW_ROOTPATH'] = TestableFramework::calculateRootPath();

require_once($_SERVER['FLOW_ROOTPATH'] . 'Packages/Framework/Neos.Flow/Classes/Core/Bootstrap.php');
$bootstrap = new \Neos\Flow\Core\Bootstrap($context);
$bootstrap->setPreselectedRequestHandlerClassName(\Neos\Flow\Tests\FunctionalTestRequestHandler::class);
$bootstrap = new Bootstrap($context);
$bootstrap->registerRequestHandler(new RuntimeSequenceHttpRequestHandler($bootstrap));
$bootstrap->setPreselectedRequestHandlerClassName(RuntimeSequenceHttpRequestHandler::class);
$bootstrap->run();
7 changes: 7 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
"symfony/console": "^5.1 || ^6.0",
"guzzlehttp/guzzle": "^6.0 || ^7.0"
},
"autoload": {
"psr-4": {
"Neos\\BuildEssentials\\": [
"Classes"
]
}
},
"extra": {
"installer-name": "BuildEssentials"
}
Expand Down

0 comments on commit 1b0d76b

Please sign in to comment.