Skip to content

Commit 1a66bb8

Browse files
exaby73pratikshazalte69
authored andcommitted
feat: Add testkit backend
1 parent 97df0ab commit 1a66bb8

File tree

91 files changed

+4412
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+4412
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ composer.lock
1414
/docs/_build
1515
cachegrind.out.*
1616
.phpunit.cache/
17+
/testkit-backend/testkit/

.php-cs-fixer.dist.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
try {
3434
$finder = PhpCsFixer\Finder::create()
3535
->in(__DIR__.'/src')
36-
->in(__DIR__.'/tests');
36+
->in(__DIR__.'/tests')
37+
->in(__DIR__.'/testkit-backend');
3738
} catch (Throwable $e) {
3839
echo $e->getMessage()."\n";
3940

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,7 @@ COPY src/ src/
2626
COPY tests/ tests/
2727
COPY .git/ .git/
2828

29+
RUN git config --global --add safe.directory /opt/project
30+
2931

3032

composer.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@
5757
"cache/integration-tests": "dev-master",
5858
"kubawerlos/php-cs-fixer-custom-fixers": "3.13.*",
5959
"rector/rector": "^1.0",
60-
"psr/log": "^3.0"
60+
"psr/log": "^3.0",
61+
"php-di/php-di": "^6.3"
6162
},
6263
"autoload": {
6364
"psr-4": {
@@ -66,7 +67,8 @@
6667
},
6768
"autoload-dev": {
6869
"psr-4": {
69-
"Laudis\\Neo4j\\Tests\\": "tests/"
70+
"Laudis\\Neo4j\\Tests\\": "tests/",
71+
"Laudis\\Neo4j\\TestkitBackend\\": "testkit-backend/src"
7072
}
7173
},
7274
"minimum-stability": "stable",
@@ -78,5 +80,9 @@
7880
"platform": {
7981
"php": "8.1.17"
8082
}
83+
},
84+
"scripts": {
85+
"fix-cs": "./vendor/bin/php-cs-fixer fix",
86+
"check-cs": "./vendor/bin/php-cs-fixer fix --dry-run"
8187
}
8288
}

docker-compose.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,13 @@ services:
115115
NEO4J_initial_server_mode__constraint: 'SECONDARY'
116116
NEO4J_server_bolt_advertised__address: server4:7687
117117
NEO4J_server_http_advertised__address: server4:7474
118+
119+
testkit_backend:
120+
<<: *common-php
121+
command: php ./testkit-backend/index.php
122+
networks:
123+
- neo4j
124+
depends_on:
125+
- neo4j
126+
ports:
127+
- "9876:9876"

testkit-backend/blacklist.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Neo4j PHP Client and Driver package.
7+
*
8+
* (c) Nagels <https://nagels.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
return [
15+
'neo4j' => [
16+
'datatypes' => [
17+
'TestDataTypes' => [
18+
'test_should_echo_very_long_map' => 'Work in progress on testkit frontend',
19+
],
20+
],
21+
'sessionrun' => [
22+
'TestSessionRun' => [
23+
'test_autocommit_transactions_should_support_metadata' => 'Meta data isn\'t supported yet',
24+
'test_autocommit_transactions_should_support_timeout' => 'Waiting on bookmarks isn\'t supported yet',
25+
],
26+
],
27+
'test_direct_driver' => [
28+
'TestDirectDriver' => [
29+
'test_custom_resolver' => 'No custom resolver implemented',
30+
'test_fail_nicely_when_using_http_port' => 'Not implemented yet',
31+
],
32+
],
33+
'test_summary' => [
34+
'TestDirectDriver' => [
35+
'test_agent_string' => 'This is not an official driver yet',
36+
],
37+
],
38+
'txrun' => [
39+
'TestTxRun' => [
40+
'test_should_fail_to_run_query_for_invalid_bookmark' => 'Waiting on bookmarks isn\'t supported yet',
41+
],
42+
],
43+
'txfuncrun' => [
44+
'TestTxFuncRun' => [
45+
'test_iteration_nested' => 'Buffers not supported yet',
46+
'test_updates_last_bookmark_on_commit' => 'Waiting on bookmarks isn\'t supported yet',
47+
],
48+
],
49+
],
50+
];

testkit-backend/features.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Neo4j PHP Client and Driver package.
7+
*
8+
* (c) Nagels <https://nagels.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
return [
15+
// === OPTIMIZATIONS ===
16+
// On receiving Neo.ClientError.Security.AuthorizationExpired, the driver
17+
// shouldn't reuse any open connections for anything other than finishing
18+
// a started job. All other connections should be re-established before
19+
// running the next job with them.
20+
'AuthorizationExpiredTreatment' => false,
21+
22+
// Driver doesn't explicitly send message data that is the default value.
23+
// This conserves bandwidth.
24+
'Optimization:ImplicitDefaultArguments' => false,
25+
26+
// The driver sends no more than the strictly necessary RESET messages.
27+
'Optimization:MinimalResets' => false,
28+
29+
// The driver caches connections (e.g., in a pool) and doesn't start a new
30+
// one (with hand-shake, HELLO, etc.) for each query.
31+
'Optimization:ConnectionReuse' => false,
32+
33+
// The driver doesn't wait for a SUCCESS after calling RUN but pipelines a
34+
// PULL right afterwards and consumes two messages after that. This saves a
35+
// full round-trip.
36+
'Optimization:PullPipelining' => false,
37+
38+
// === CONFIGURATION HINTS (BOLT 4.3+) ===
39+
// The driver understands and follow the connection hint
40+
// connection.recv_timeout_seconds which tells it to close the connection
41+
// after not receiving an answer on any request for longer than the given
42+
// time period. On timout, the driver should remove the server from its
43+
// routing table and assume all other connections to the server are dead
44+
// as well.
45+
'ConfHint:connection.recv_timeout_seconds' => false,
46+
47+
// Temporary driver feature that will be removed when all official drivers
48+
// have been unified in their behaviour of when they return a Result object.
49+
// We aim for drivers to not providing a Result until the server replied with
50+
// SUCCESS so that the result keys are already known and attached to the
51+
// Result object without further waiting or communication with the server.
52+
'Temporary:ResultKeys' => false,
53+
54+
// Temporary driver feature that will be removed when all official driver
55+
// backends have implemented all summary response fields.
56+
'Temporary:FullSummary' => false,
57+
58+
// Temporary driver feature that will be removed when all official driver
59+
// backends have implemented path and relationship types
60+
'Temporary:CypherPathAndRelationship' => true,
61+
];

testkit-backend/index.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Neo4j PHP Client and Driver package.
7+
*
8+
* (c) Nagels <https://nagels.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
require_once __DIR__.'/../vendor/autoload.php';
15+
16+
use Laudis\Neo4j\TestkitBackend\Backend;
17+
18+
$backend = Backend::boot();
19+
while (true) {
20+
$backend->handle();
21+
}

testkit-backend/register.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Neo4j PHP Client and Driver package.
7+
*
8+
* (c) Nagels <https://nagels.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
use Laudis\Neo4j\TestkitBackend\Handlers\GetFeatures;
15+
use Laudis\Neo4j\TestkitBackend\Handlers\StartTest;
16+
use Laudis\Neo4j\TestkitBackend\MainRepository;
17+
use Monolog\Handler\StreamHandler;
18+
use Monolog\Logger;
19+
use Psr\Log\LoggerInterface;
20+
21+
return [
22+
LoggerInterface::class => static function () {
23+
$logger = new Logger('testkit-backend');
24+
$logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
25+
26+
return $logger;
27+
},
28+
29+
GetFeatures::class => static function () {
30+
$featuresConfig = require __DIR__.'/features.php';
31+
32+
return new GetFeatures($featuresConfig);
33+
},
34+
35+
StartTest::class => static function () {
36+
$acceptedTests = require __DIR__.'/blacklist.php';
37+
38+
return new StartTest($acceptedTests);
39+
},
40+
41+
MainRepository::class => static function () {
42+
return new MainRepository(
43+
[],
44+
[],
45+
[],
46+
[],
47+
);
48+
},
49+
];

testkit-backend/src/Backend.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Neo4j PHP Client and Driver package.
7+
*
8+
* (c) Nagels <https://nagels.tech>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Laudis\Neo4j\TestkitBackend;
15+
16+
use DI\ContainerBuilder;
17+
use Exception;
18+
19+
use function get_debug_type;
20+
use function json_decode;
21+
use function json_encode;
22+
23+
use const JSON_THROW_ON_ERROR;
24+
25+
use JsonException;
26+
use Laudis\Neo4j\TestkitBackend\Contracts\RequestHandlerInterface;
27+
use Laudis\Neo4j\TestkitBackend\Contracts\TestkitResponseInterface;
28+
use Laudis\Neo4j\TestkitBackend\Responses\BackendErrorResponse;
29+
30+
use const PHP_EOL;
31+
32+
use Psr\Container\ContainerInterface;
33+
use Psr\Log\LoggerInterface;
34+
use Throwable;
35+
use UnexpectedValueException;
36+
37+
final class Backend
38+
{
39+
private Socket $socket;
40+
private LoggerInterface $logger;
41+
private ContainerInterface $container;
42+
private RequestFactory $factory;
43+
44+
public function __construct(
45+
Socket $socket,
46+
LoggerInterface $logger,
47+
ContainerInterface $container,
48+
RequestFactory $factory,
49+
) {
50+
$this->socket = $socket;
51+
$this->logger = $logger;
52+
$this->container = $container;
53+
$this->factory = $factory;
54+
}
55+
56+
/**
57+
* @throws Exception
58+
*/
59+
public static function boot(): self
60+
{
61+
$builder = new ContainerBuilder();
62+
$builder->addDefinitions(__DIR__.'/../register.php');
63+
$builder->useAutowiring(true);
64+
$container = $builder->build();
65+
66+
$logger = $container->get(LoggerInterface::class);
67+
$logger->info('Booting testkit backend ...');
68+
Socket::setupEnvironment();
69+
$tbr = new self(Socket::fromEnvironment(), $logger, $container, new RequestFactory());
70+
$logger->info('Testkit booted');
71+
72+
return $tbr;
73+
}
74+
75+
/**
76+
* @throws JsonException
77+
*/
78+
public function handle(): void
79+
{
80+
while (true) {
81+
$message = $this->socket->readMessage();
82+
if ($message === null) {
83+
$this->socket->reset();
84+
continue;
85+
}
86+
87+
try {
88+
[$handler, $request] = $this->extractRequest($message);
89+
$this->properSendoff($handler->handle($request));
90+
} catch (Throwable $e) {
91+
$this->logger->error($e->__toString());
92+
$this->properSendoff(new BackendErrorResponse($e->getMessage()));
93+
}
94+
}
95+
}
96+
97+
private function loadRequestHandler(string $name): RequestHandlerInterface
98+
{
99+
$action = $this->container->get('Laudis\\Neo4j\\TestkitBackend\\Handlers\\'.$name);
100+
if (!$action instanceof RequestHandlerInterface) {
101+
$str = printf(
102+
'Expected action to be an instance of %s, received %s instead',
103+
RequestHandlerInterface::class,
104+
get_debug_type($action)
105+
);
106+
throw new UnexpectedValueException($str);
107+
}
108+
109+
return $action;
110+
}
111+
112+
private function properSendoff(TestkitResponseInterface $response): void
113+
{
114+
$message = json_encode($response, JSON_THROW_ON_ERROR);
115+
116+
$this->logger->debug('Sending: '.$message);
117+
$this->socket->write('#response begin'.PHP_EOL);
118+
$this->socket->write($message.PHP_EOL);
119+
$this->socket->write('#response end'.PHP_EOL);
120+
}
121+
122+
/**
123+
* @return array{0: RequestHandlerInterface, 1: object}
124+
*/
125+
private function extractRequest(string $message): array
126+
{
127+
$this->logger->debug('Received: '.$message);
128+
/** @var array{name: string, data: iterable<array|scalar|null>} $response */
129+
$response = json_decode($message, true, 512, JSON_THROW_ON_ERROR);
130+
131+
$handler = $this->loadRequestHandler($response['name']);
132+
$request = $this->factory->create($response['name'], $response['data']);
133+
134+
return [$handler, $request];
135+
}
136+
}

0 commit comments

Comments
 (0)