Skip to content

Commit 8c44033

Browse files
authored
feat: clock testing DX (#620)
* feat: clock testing DX * fixes * fixes * fixes * fixes * fixes * fixes
1 parent 95faa20 commit 8c44033

24 files changed

+269
-210
lines changed

docker-compose.yml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ services:
77
- "$PWD:/data/app"
88
working_dir: "/data/app"
99
command: sleep 99999
10-
container_name: "ecotone_development"
10+
container_name: "${PHP_CONTAINER_NAME:-ecotone_development}"
1111
user: "${USER_PID:-1000}:${USER_PID:-1000}"
1212
extra_hosts:
1313
- "host.docker.internal:host-gateway"
@@ -32,7 +32,7 @@ services:
3232
- "$PWD:/data/app"
3333
working_dir: "/data/app"
3434
command: sleep 99999
35-
container_name: "ecotone_development_8_2"
35+
container_name: "${PHP_8_2_CONTAINER_NAME:-ecotone_development_8_2}"
3636
user: "${USER_PID:-1000}:${USER_PID:-1000}"
3737
extra_hosts:
3838
- "host.docker.internal:host-gateway"
@@ -73,16 +73,16 @@ services:
7373
RABBITMQ_DEFAULT_USER: guest
7474
RABBITMQ_DEFAULT_PASS: guest
7575
ports:
76-
- "15672:15672"
77-
- "5672:5672"
76+
- '${RABBITMQ_PORT:-5672}:5672'
77+
- '${RABBITMQ_MGMT_PORT:-15672}:15672'
7878
localstack:
7979
image: localstack/localstack:3.0.0
8080
environment:
8181
LOCALSTACK_HOST: 'localstack'
8282
SERVICES: 'sqs,sns'
8383
ports:
84-
- "4566:4566" # LocalStack Gateway
85-
- "4510-4559:4510-4559" # external services port range
84+
- "${LOCALSTACK_PORT:-4566}:4566" # LocalStack Gateway
85+
# - "4510-4559:4510-4559" # external services port range
8686
redis:
8787
image: redis:7-alpine
8888
ports:
@@ -96,33 +96,33 @@ services:
9696
- ./.docker/collector/otel-collector-config.yaml:/etc/otel-collector-config.yml
9797
ports:
9898
- "9411" # Zipkin receiver
99-
- "4317:4317" # OTLP gRPC receiver
100-
- "4318:4318" # OTLP/HTTP receiver
99+
# - "4317:4317" # OTLP gRPC receiver
100+
# - "4318:4318" # OTLP/HTTP receiver
101101
zipkin:
102102
image: openzipkin/zipkin-slim
103103
networks:
104104
- default
105105
ports:
106-
- 9411:9411
106+
- '${ZIPKIN_PORT:-9411}:9411'
107107
jaeger:
108108
image: jaegertracing/all-in-one:latest
109109
environment:
110110
COLLECTOR_OTLP_ENABLED: "true"
111111
networks:
112112
- default
113113
ports:
114-
- 16686:16686
114+
- '${JAEGER_PORT:-16686}:16686'
115115
kafka:
116116
image: 'apache/kafka:3.9.0'
117117
ports:
118-
- '9094:9092'
118+
- '${KAFKA_PORT:-9094}:9092'
119119
environment:
120120
- KAFKA_NODE_ID=0
121121
- KAFKA_PROCESS_ROLES=broker,controller
122122
- KAFKA_CONTROLLER_QUORUM_VOTERS=0@kafka:9093
123123
- KAFKA_CONTROLLER_LISTENER_NAMES=CONTROLLER
124-
- KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
125-
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://localhost:9094
124+
- KAFKA_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:${KAFKA_PORT:-9094}
125+
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://localhost:${KAFKA_PORT:-9094}
126126
- KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
127127
- KAFKA_AUTO_CREATE_TOPICS_ENABLE=true
128128
- KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR=1
@@ -133,7 +133,7 @@ services:
133133
kafdrop:
134134
image: 'obsidiandynamics/kafdrop:latest'
135135
ports:
136-
- '9999:9000'
136+
- '${KAFDROP_PORT:-9999}:9000'
137137
environment:
138138
- KAFKA_BROKERCONNECT=kafka:9092
139139
networks:

packages/Dbal/src/EnqueueDbal/DbalContext.php

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ class DbalContext implements Context
4141
* @var array
4242
*/
4343
private $config;
44-
private EcotoneClockInterface $clock;
4544

4645
/**
4746
* Callable must return instance of Doctrine\DBAL\Connection once called.
@@ -63,8 +62,6 @@ public function __construct($connection, array $config = [])
6362
} else {
6463
throw new InvalidArgumentException(sprintf('The connection argument must be either %s or callable that returns %s.', Connection::class, Connection::class));
6564
}
66-
67-
$this->clock = Clock::get();
6865
}
6966

7067
/**
@@ -257,6 +254,6 @@ public function createDataBaseTable(): void
257254

258255
public function getClock(): EcotoneClockInterface
259256
{
260-
return $this->clock;
257+
return Clock::get();
261258
}
262259
}

packages/Dbal/tests/Integration/DbalBackedMessageChannelTest.php

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
use Ecotone\Messaging\PollableChannel;
1616
use Ecotone\Messaging\Scheduling\Clock;
1717
use Ecotone\Messaging\Scheduling\Duration;
18+
use Ecotone\Messaging\Scheduling\EcotoneClockInterface;
1819
use Ecotone\Messaging\Scheduling\StubUTCClock;
20+
use Ecotone\Messaging\Scheduling\TimeSpan;
1921
use Ecotone\Messaging\Support\MessageBuilder;
2022
use Ecotone\Test\ClockSensitiveTrait;
2123
use Ecotone\Test\StubLogger;
@@ -35,8 +37,6 @@
3537
*/
3638
class DbalBackedMessageChannelTest extends DbalMessagingTestCase
3739
{
38-
use ClockSensitiveTrait;
39-
4040
public function test_sending_and_receiving_via_channel()
4141
{
4242
$channelName = Uuid::uuid4()->toString();
@@ -196,7 +196,7 @@ public function test_reconnecting_on_disconnected_channel_with_manager_registry(
196196
$this->assertNotNull($receivedMessage, 'Not received message');
197197
}
198198

199-
public function test_delaying_the_message()
199+
public function test_delaying_the_message_with_custom_clock()
200200
{
201201
$channelName = Uuid::uuid4()->toString();
202202
$clock = new StubUTCClock();
@@ -217,8 +217,6 @@ public function test_delaying_the_message()
217217
/** @var PollableChannel $messageChannel */
218218
$messageChannel = $ecotoneLite->getMessageChannel($channelName);
219219

220-
Clock::set($clock);
221-
222220
$messageChannel->send(
223221
MessageBuilder::withPayload('some')
224222
->setHeader(MessageHeaders::DELIVERY_DELAY, 2000)
@@ -232,6 +230,76 @@ public function test_delaying_the_message()
232230
$this->assertNotNull($messageChannel->receive());
233231
}
234232

233+
public function test_delaying_the_message_with_native_clock()
234+
{
235+
$channelName = Uuid::uuid4()->toString();
236+
237+
$ecotoneLite = EcotoneLite::bootstrapFlowTesting(
238+
containerOrAvailableServices: [
239+
DbalConnectionFactory::class => $this->getConnectionFactory(true),
240+
],
241+
configuration: ServiceConfiguration::createWithDefaults()
242+
->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::DBAL_PACKAGE]))
243+
->withExtensionObjects([
244+
DbalBackedMessageChannelBuilder::create($channelName)
245+
->withReceiveTimeout(1),
246+
])
247+
);
248+
249+
/** @var PollableChannel $messageChannel */
250+
$messageChannel = $ecotoneLite->getMessageChannel($channelName);
251+
252+
$messageChannel->send(
253+
MessageBuilder::withPayload('some')
254+
->setHeader(MessageHeaders::DELIVERY_DELAY, 2000)
255+
->build()
256+
);
257+
258+
$ecotoneLite->waitTill(TimeSpan::withSeconds(1));
259+
260+
$this->assertNull($messageChannel->receive());
261+
262+
$ecotoneLite->waitTill(TimeSpan::withSeconds(3));
263+
264+
$this->assertNotNull($messageChannel->receive());
265+
}
266+
267+
public function test_delaying_the_message_with_native_clock_using_date_time()
268+
{
269+
$channelName = Uuid::uuid4()->toString();
270+
271+
$ecotoneLite = EcotoneLite::bootstrapFlowTesting(
272+
containerOrAvailableServices: [
273+
DbalConnectionFactory::class => $this->getConnectionFactory(true),
274+
],
275+
configuration: ServiceConfiguration::createWithDefaults()
276+
->withSkippedModulePackageNames(ModulePackageList::allPackagesExcept([ModulePackageList::DBAL_PACKAGE]))
277+
->withExtensionObjects([
278+
DbalBackedMessageChannelBuilder::create($channelName)
279+
->withReceiveTimeout(1),
280+
])
281+
);
282+
283+
/** @var PollableChannel $messageChannel */
284+
$messageChannel = $ecotoneLite->getMessageChannel($channelName);
285+
286+
$messageChannel->send(
287+
MessageBuilder::withPayload('some')
288+
->setHeader(MessageHeaders::DELIVERY_DELAY, 2000)
289+
->build()
290+
);
291+
292+
/** @var EcotoneClockInterface $clock */
293+
$clock = $ecotoneLite->getServiceFromContainer(EcotoneClockInterface::class);
294+
$ecotoneLite->waitTill($clock->now()->add(Duration::seconds(1)));
295+
296+
$this->assertNull($messageChannel->receive());
297+
298+
$ecotoneLite->waitTill($clock->now()->add(Duration::seconds(3)));
299+
300+
$this->assertNotNull($messageChannel->receive());
301+
}
302+
235303
public function test_sending_message()
236304
{
237305
$queueName = Uuid::uuid4()->toString();

packages/Ecotone/src/Lite/EcotoneLite.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Ecotone\Messaging\Config\ServiceConfiguration;
2323
use Ecotone\Messaging\ConfigurationVariableService;
2424
use Ecotone\Messaging\InMemoryConfigurationVariableService;
25+
use Ecotone\Messaging\Scheduling\Clock;
2526
use Ecotone\Messaging\Support\Assert;
2627
use Ecotone\Modelling\BaseEventSourcingConfiguration;
2728

packages/Ecotone/src/Lite/Test/Configuration/DelayedMessageReleaseHandler.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@ final class DelayedMessageReleaseHandler
1818
{
1919
public function releaseMessagesAwaitingFor(string $channelName, int|TimeSpan|DateTimeInterface $timeInMillisecondsOrDateTime, ChannelResolver $channelResolver): void
2020
{
21+
if (!$channelResolver->hasChannelWithName($channelName)) {
22+
return;
23+
}
24+
2125
/** @var DelayableQueueChannel|MessageChannelInterceptorAdapter $channel */
2226
$channel = $channelResolver->resolve($channelName);
2327
if ($channel instanceof MessageChannelInterceptorAdapter) {
2428
$channel = $channel->getInternalMessageChannel();
2529
}
2630

27-
Assert::isTrue($channel instanceof DelayableQueueChannel, sprintf('Used %s channel to release delayed message, use instead of %s.', $channel::class, DelayableQueueChannel::class));
31+
if (! $channel instanceof DelayableQueueChannel) {
32+
return;
33+
}
2834

2935
$channel->releaseMessagesAwaitingFor($timeInMillisecondsOrDateTime);
3036
}

packages/Ecotone/src/Lite/Test/ConfiguredMessagingSystemWithTestSupport.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Ecotone\Messaging\MessageChannel;
1313
use Ecotone\Messaging\MessageHeaders;
1414
use Ecotone\Messaging\MessagePublisher;
15+
use Ecotone\Messaging\Scheduling\Clock;
16+
use Ecotone\Messaging\Scheduling\EcotoneClockInterface;
1517
use Ecotone\Modelling\AggregateFlow\SaveAggregate\AggregateResolver\AggregateDefinitionRegistry;
1618
use Ecotone\Modelling\CommandBus;
1719
use Ecotone\Modelling\DistributedBus;
@@ -89,6 +91,7 @@ public function getFlowTestSupport(): FlowTestSupport
8991
$this->getServiceFromContainer(AggregateDefinitionRegistry::class),
9092
$this->getMessagingTestSupport(),
9193
$this->getGatewayByName(MessagingEntrypoint::class),
94+
$this->getServiceFromContainer(EcotoneClockInterface::class),
9295
$this->configuredMessagingSystem
9396
);
9497
}

packages/Ecotone/src/Lite/Test/FlowTestSupport.php

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
use Ecotone\Messaging\MessageHeaders;
1818
use Ecotone\Messaging\MessagingException;
1919
use Ecotone\Messaging\PollableChannel;
20+
use Ecotone\Messaging\Scheduling\Clock;
21+
use Ecotone\Messaging\Scheduling\Duration;
22+
use Ecotone\Messaging\Scheduling\EcotoneClockInterface;
2023
use Ecotone\Messaging\Scheduling\TimeSpan;
2124
use Ecotone\Messaging\Support\Assert;
2225
use Ecotone\Messaging\Support\MessageBuilder;
@@ -46,6 +49,7 @@ public function __construct(
4649
private AggregateDefinitionRegistry $aggregateDefinitionRegistry,
4750
private MessagingTestSupport $testSupportGateway,
4851
private MessagingEntrypoint $messagingEntrypoint,
52+
private EcotoneClockInterface $clock,
4953
private ConfiguredMessagingSystem $configuredMessagingSystem
5054
) {
5155
}
@@ -141,9 +145,7 @@ public function receiveMessageFrom(string $channelName): ?Message
141145
*/
142146
public function run(string $name, ?ExecutionPollingMetadata $executionPollingMetadata = null, TimeSpan|DateTimeInterface|null $releaseAwaitingFor = null): self
143147
{
144-
if ($releaseAwaitingFor) {
145-
$this->testSupportGateway->releaseMessagesAwaitingFor($name, $releaseAwaitingFor);
146-
}
148+
$this->testSupportGateway->releaseMessagesAwaitingFor($name, $releaseAwaitingFor ?? Clock::get()->now());
147149
$this->configuredMessagingSystem->run($name, $executionPollingMetadata);
148150

149151
return $this;
@@ -190,6 +192,22 @@ public function getEventStreamEvents(string $streamName): array
190192
return $this->getGateway(EventStore::class)->load($streamName);
191193
}
192194

195+
public function waitTill(TimeSpan|DateTimeInterface $time): self
196+
{
197+
if ($time instanceof DateTimeInterface) {
198+
if ($time < $this->clock->now()) {
199+
throw new MessagingException("Time to wait is in the past. Now: {$this->clock->now()}, time to wait: {$time}");
200+
}
201+
}
202+
203+
$this->clock->sleep($time instanceof TimeSpan
204+
? $time->toDuration()
205+
: Timespan::fromDateInterval($time->diff($this->clock->now()))->toDuration()
206+
);
207+
208+
return $this;
209+
}
210+
193211
/**
194212
* @param Event[]|object[]|array[] $events
195213
*/

packages/Ecotone/src/Messaging/Config/Container/Compiler/RegisterSingletonMessagingServices.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use Ecotone\Messaging\Config\Container\Reference;
1111
use Ecotone\Messaging\Config\Container\ReferenceSearchServiceWithContainer;
1212
use Ecotone\Messaging\Config\MessagingSystemContainer;
13+
use Ecotone\Messaging\Config\ModulePackageList;
1314
use Ecotone\Messaging\Config\ServiceCacheConfiguration;
15+
use Ecotone\Messaging\Config\ServiceConfiguration;
1416
use Ecotone\Messaging\Handler\Bridge\Bridge;
1517
use Ecotone\Messaging\Handler\ChannelResolver;
1618
use Ecotone\Messaging\Handler\Enricher\PropertyEditorAccessor;
@@ -30,11 +32,23 @@
3032
*/
3133
class RegisterSingletonMessagingServices implements CompilerPass
3234
{
35+
public function __construct(
36+
private ServiceConfiguration $serviceConfiguration,
37+
) {
38+
}
39+
3340
public function process(ContainerBuilder $builder): void
3441
{
3542
$this->registerDefault($builder, Bridge::class, new Definition(Bridge::class));
3643
$this->registerDefault($builder, Reference::toChannel(NullableMessageChannel::CHANNEL_NAME), new Definition(NullableMessageChannel::class));
37-
$this->registerDefault($builder, EcotoneClockInterface::class, new Definition(Clock::class, [new Reference(ClockInterface::class, ContainerImplementation::NULL_ON_INVALID_REFERENCE)]));
44+
$this->registerDefault($builder, EcotoneClockInterface::class, new Definition(
45+
Clock::class,
46+
[
47+
new Reference(ClockInterface::class, ContainerImplementation::NULL_ON_INVALID_REFERENCE),
48+
$this->serviceConfiguration->isModulePackageEnabled(ModulePackageList::TEST_PACKAGE),
49+
],
50+
factory: [Clock::class, 'createBasedOnConfig']
51+
));
3852
$this->registerDefault($builder, ChannelResolver::class, new Definition(ChannelResolverWithContainer::class, [new Reference(ContainerInterface::class)]));
3953
$this->registerDefault($builder, ReferenceSearchService::class, new Definition(ReferenceSearchServiceWithContainer::class, [new Reference(ContainerInterface::class)]));
4054
$this->registerDefault($builder, ExpressionEvaluationService::REFERENCE, new Definition(SymfonyExpressionEvaluationAdapter::class, [new Reference(ReferenceSearchService::class)], 'create'));

packages/Ecotone/src/Messaging/Config/MessagingSystemConfiguration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -989,7 +989,7 @@ public function process(ContainerBuilder $builder): void
989989
}
990990

991991
$messagingBuilder->register(ConfiguredMessagingSystem::class, new Definition(MessagingSystemContainer::class, [new Reference(ContainerInterface::class), $messagingBuilder->getPollingEndpoints(), $gatewayListReferences]));
992-
(new RegisterSingletonMessagingServices())->process($builder);
992+
(new RegisterSingletonMessagingServices($this->applicationConfiguration))->process($builder);
993993
foreach ($this->compilerPasses as $compilerPass) {
994994
$compilerPass->process($builder);
995995
}

0 commit comments

Comments
 (0)