This is an automated email from the ASF dual-hosted git repository.
gkoszyk pushed a commit to branch io_uring_tpc
in repository https://gitbox.apache.org/repos/asf/iggy.git
The following commit(s) were added to refs/heads/io_uring_tpc by this push:
new 4ae1fbcd feat(io_uring): adapt rust, python and csharp bdd tests to
new interface (#2241)
4ae1fbcd is described below
commit 4ae1fbcdba0076367656e38620c8b0e87fd1c703
Author: Hubert Gruszecki <[email protected]>
AuthorDate: Mon Oct 6 17:07:38 2025 +0200
feat(io_uring): adapt rust, python and csharp bdd tests to new interface
(#2241)
---
bdd/docker-compose.yml | 43 ++++++++----------
bdd/python/Dockerfile | 4 +-
bdd/python/tests/test_basic_messaging.py | 35 +++++++--------
bdd/rust/tests/steps/streams.rs | 16 +++----
bdd/rust/tests/steps/topics.rs | 16 ++-----
bdd/scenarios/basic_messaging.feature | 12 ++---
core/server/src/tcp/tcp_listener.rs | 1 -
.../Iggy_SDK.Tests.BDD/Context/TestContext.cs | 51 +++++++---------------
.../BasicMessagingOperationsSteps.cs | 21 ++++-----
foreign/python/src/client.rs | 23 +++++-----
scripts/run-bdd-tests.sh | 15 +++++++
11 files changed, 99 insertions(+), 138 deletions(-)
diff --git a/bdd/docker-compose.yml b/bdd/docker-compose.yml
index 95d64d44..40fd2c7a 100644
--- a/bdd/docker-compose.yml
+++ b/bdd/docker-compose.yml
@@ -27,10 +27,22 @@ services:
PREBUILT_IGGY_CLI: ${IGGY_CLI_PATH:-target/debug/iggy}
LIBC: glibc
PROFILE: debug
- command: ["--fresh"]
+ command: ["--fresh", "--with-default-root-credentials"]
+ cap_add:
+ - SYS_NICE
+ security_opt:
+ - seccomp:unconfined
+ ulimits:
+ memlock:
+ soft: -1
+ hard: -1
+ healthcheck:
+ test: ["CMD", "/usr/local/bin/iggy", "ping"]
+ interval: 1s
+ timeout: 3s
+ retries: 30
+ start_period: 2s
environment:
- - IGGY_ROOT_USERNAME=iggy
- - IGGY_ROOT_PASSWORD=iggy
- RUST_LOG=info
- IGGY_SYSTEM_PATH=local_data
- IGGY_TCP_ADDRESS=0.0.0.0:8090
@@ -105,7 +117,8 @@ services:
context: ..
dockerfile: bdd/node/Dockerfile
depends_on:
- - iggy-server
+ iggy-server:
+ condition: service_healthy
environment:
- IGGY_ROOT_USERNAME=iggy
- IGGY_ROOT_PASSWORD=iggy
@@ -132,27 +145,5 @@ networks:
iggy-bdd-network:
driver: bridge
- node-bdd:
- build:
- context: ..
- dockerfile: bdd/node/Dockerfile
- depends_on:
- iggy-server:
- condition: service_healthy
- environment:
- - IGGY_TCP_ADDRESS=iggy-server:8090
- command: ["npm", "run", "test:bdd"]
-
- csharp-bdd:
- build:
- context: ..
- dockerfile: bdd/csharp/Dockerfile
- depends_on:
- iggy-server:
- condition: service_healthy
- environment:
- - IGGY_TCP_ADDRESS=iggy-server:8090
- command: ["dotnet", "test"]
-
volumes:
iggy_data:
diff --git a/bdd/python/Dockerfile b/bdd/python/Dockerfile
index 7b7b3770..4ee7961a 100644
--- a/bdd/python/Dockerfile
+++ b/bdd/python/Dockerfile
@@ -41,8 +41,8 @@ WORKDIR /workspace/foreign/python
RUN pip3 install --no-cache-dir -r /workspace/bdd/python/requirements.txt
# Build and install the Iggy Python SDK
-RUN maturin build --out dist && \
- pip3 install dist/*.whl
+RUN maturin build --interpreter python3 --out dist && \
+ pip3 install dist/*cp313*.whl
# Set up BDD test directory
WORKDIR /app
diff --git a/bdd/python/tests/test_basic_messaging.py
b/bdd/python/tests/test_basic_messaging.py
index 9abd177c..ea5cc8ed 100644
--- a/bdd/python/tests/test_basic_messaging.py
+++ b/bdd/python/tests/test_basic_messaging.py
@@ -65,12 +65,12 @@ def no_streams_in_system(context):
pass
-@when(parsers.parse('I create a stream with ID {stream_id:d} and name
{stream_name}'))
-def create_stream(context, stream_id, stream_name):
- """Create a stream with specified ID and name"""
+@when(parsers.parse('I create a stream with name {stream_name}'))
+def create_stream(context, stream_name):
+ """Create a stream with specified name"""
async def _create():
- await context.client.create_stream(name=stream_name,
stream_id=stream_id)
- context.last_stream_id = stream_id
+ stream = await context.client.create_stream(name=stream_name)
+ context.last_stream_id = stream.id
context.last_stream_name = stream_name
asyncio.run(_create())
@@ -86,29 +86,27 @@ def stream_created_successfully(context):
asyncio.run(_verify())
-@then(parsers.parse('the stream should have ID {stream_id:d} and name
{stream_name}'))
-def verify_stream_properties(context, stream_id, stream_name):
- """Verify stream has correct ID and name"""
+@then(parsers.parse('the stream should have name {stream_name}'))
+def verify_stream_properties(context, stream_name):
+ """Verify stream has correct name"""
async def _verify():
stream = await context.client.get_stream(stream_name)
assert stream is not None
- assert stream.id == stream_id
assert stream.name == stream_name
asyncio.run(_verify())
-@when(parsers.parse('I create a topic with ID {topic_id:d} and name
{topic_name} in stream {stream_id:d} with {partitions:d} partitions'))
-def create_topic(context, topic_id, topic_name, stream_id, partitions):
+@when(parsers.parse('I create a topic with name {topic_name} in stream
{stream_id:d} with {partitions:d} partitions'))
+def create_topic(context, topic_name, stream_id, partitions):
"""Create a topic with specified parameters"""
async def _create():
- await context.client.create_topic(
+ topic = await context.client.create_topic(
stream=stream_id,
name=topic_name,
- partitions_count=partitions,
- topic_id=topic_id
+ partitions_count=partitions
)
- context.last_topic_id = topic_id
+ context.last_topic_id = topic.id
context.last_topic_name = topic_name
context.last_topic_partitions = partitions
@@ -125,13 +123,12 @@ def topic_created_successfully(context):
asyncio.run(_verify())
-@then(parsers.parse('the topic should have ID {topic_id:d} and name
{topic_name}'))
-def verify_topic_properties(context, topic_id, topic_name):
- """Verify topic has correct ID and name"""
+@then(parsers.parse('the topic should have name {topic_name}'))
+def verify_topic_properties(context, topic_name):
+ """Verify topic has correct name"""
async def _verify():
topic = await context.client.get_topic(context.last_stream_id,
topic_name)
assert topic is not None
- assert topic.id == topic_id
assert topic.name == topic_name
asyncio.run(_verify())
diff --git a/bdd/rust/tests/steps/streams.rs b/bdd/rust/tests/steps/streams.rs
index f64f680e..f24a6791 100644
--- a/bdd/rust/tests/steps/streams.rs
+++ b/bdd/rust/tests/steps/streams.rs
@@ -33,11 +33,11 @@ pub async fn given_no_streams(world: &mut GlobalContext) {
);
}
-#[when(regex = r"^I create a stream with ID (\d+) and name (.+)$")]
-pub async fn when_create_stream(world: &mut GlobalContext, stream_id: u32,
stream_name: String) {
+#[when(regex = r"^I create a stream with name (.+)$")]
+pub async fn when_create_stream(world: &mut GlobalContext, stream_name:
String) {
let client = world.client.as_ref().expect("Client should be available");
let stream = client
- .create_stream(&stream_name, Some(stream_id))
+ .create_stream(&stream_name)
.await
.expect("Should be able to create stream");
@@ -53,18 +53,12 @@ pub async fn then_stream_created_successfully(world: &mut
GlobalContext) {
);
}
-#[then(regex = r"^the stream should have ID (\d+) and name (.+)$")]
-pub async fn then_stream_has_id_and_name(
- world: &mut GlobalContext,
- expected_id: u32,
- expected_name: String,
-) {
- let stream_id = world.last_stream_id.expect("Stream should exist");
+#[then(regex = r"^the stream should have name (.+)$")]
+pub async fn then_stream_has_name(world: &mut GlobalContext, expected_name:
String) {
let stream_name = world
.last_stream_name
.as_ref()
.expect("Stream should exist");
- assert_eq!(stream_id, expected_id, "Stream should have expected ID");
assert_eq!(
stream_name, &expected_name,
"Stream should have expected name"
diff --git a/bdd/rust/tests/steps/topics.rs b/bdd/rust/tests/steps/topics.rs
index 93557663..63150f32 100644
--- a/bdd/rust/tests/steps/topics.rs
+++ b/bdd/rust/tests/steps/topics.rs
@@ -20,12 +20,9 @@ use crate::common::global_context::GlobalContext;
use cucumber::{then, when};
use iggy::prelude::{CompressionAlgorithm, Identifier, IggyExpiry,
MaxTopicSize, TopicClient};
-#[when(
- regex = r"^I create a topic with ID (\d+) and name (.+) in stream (\d+)
with (\d+) partitions$"
-)]
+#[when(regex = r"^I create a topic with name (.+) in stream (\d+) with (\d+)
partitions$")]
pub async fn when_create_topic(
world: &mut GlobalContext,
- topic_id: u32,
topic_name: String,
stream_id: u32,
partitions_count: u32,
@@ -38,7 +35,6 @@ pub async fn when_create_topic(
partitions_count,
CompressionAlgorithm::default(),
None,
- Some(topic_id),
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
@@ -58,15 +54,9 @@ pub async fn then_topic_created_successfully(world: &mut
GlobalContext) {
);
}
-#[then(regex = r"^the topic should have ID (\d+) and name (.+)$")]
-pub async fn then_topic_has_id_and_name(
- world: &mut GlobalContext,
- expected_id: u32,
- expected_name: String,
-) {
- let topic_id = world.last_topic_id.expect("Topic should exist");
+#[then(regex = r"^the topic should have name (.+)$")]
+pub async fn then_topic_has_name(world: &mut GlobalContext, expected_name:
String) {
let topic_name = world.last_topic_name.as_ref().expect("Topic should
exist");
- assert_eq!(topic_id, expected_id, "Topic should have expected ID");
assert_eq!(
topic_name, &expected_name,
"Topic should have expected name"
diff --git a/bdd/scenarios/basic_messaging.feature
b/bdd/scenarios/basic_messaging.feature
index 1709626e..c13109dc 100644
--- a/bdd/scenarios/basic_messaging.feature
+++ b/bdd/scenarios/basic_messaging.feature
@@ -26,19 +26,19 @@ Feature: Basic Messaging Operations
Scenario: Create stream and send messages
Given I have no streams in the system
- When I create a stream with ID 1 and name "test-stream"
+ When I create a stream with name "test-stream"
Then the stream should be created successfully
- And the stream should have ID 1 and name "test-stream"
+ And the stream should have name "test-stream"
- When I create a topic with ID 1 and name "test-topic" in stream 1 with 3
partitions
+ When I create a topic with name "test-topic" in stream 0 with 3 partitions
Then the topic should be created successfully
- And the topic should have ID 1 and name "test-topic"
+ And the topic should have name "test-topic"
And the topic should have 3 partitions
- When I send 10 messages to stream 1, topic 1, partition 1
+ When I send 10 messages to stream 0, topic 0, partition 0
Then all messages should be sent successfully
- When I poll messages from stream 1, topic 1, partition 1 starting from
offset 0
+ When I poll messages from stream 0, topic 0, partition 0 starting from
offset 0
Then I should receive 10 messages
And the messages should have sequential offsets from 0 to 9
And each message should have the expected payload content
diff --git a/core/server/src/tcp/tcp_listener.rs
b/core/server/src/tcp/tcp_listener.rs
index 5332ec19..335bb8e4 100644
--- a/core/server/src/tcp/tcp_listener.rs
+++ b/core/server/src/tcp/tcp_listener.rs
@@ -89,7 +89,6 @@ pub async fn start(
format!("Failed to bind {server_name} server to address: {addr},
{err}")
})?;
let actual_addr = listener.local_addr().map_err(|e| {
- // TODO(hubcio): macro doesn't work properly with syntax like {e}
shard_error!(shard.id, "Failed to get local address: {}", e);
IggyError::CannotBindToSocket(addr.to_string())
})?;
diff --git a/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
b/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
index 2c345e6c..a30bdcb3 100644
--- a/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
+++ b/foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
@@ -1,31 +1,26 @@
-// // Licensed to the Apache Software Foundation (ASF) under one
-// // or more contributor license agreements. See the NOTICE file
-// // distributed with this work for additional information
-// // regarding copyright ownership. The ASF licenses this file
-// // to you under the Apache License, Version 2.0 (the
-// // "License"); you may not use this file except in compliance
-// // with the License. You may obtain a copy of the License at
-// //
-// // http://www.apache.org/licenses/LICENSE-2.0
-// //
-// // Unless required by applicable law or agreed to in writing,
-// // software distributed under the License is distributed on an
-// // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// // KIND, either express or implied. See the License for the
-// // specific language governing permissions and limitations
-// // under the License.
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
-<<<<<<<< HEAD:foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
using Apache.Iggy.Contracts;
using Apache.Iggy.IggyClient;
using Apache.Iggy.Messages;
-========
-using Apache.Iggy.Enums;
->>>>>>>>
master:foreign/csharp/Iggy_SDK.Tests.Integration/Helpers/NameIdHelpers.cs
namespace Apache.Iggy.Tests.BDD.Context;
-<<<<<<<< HEAD:foreign/csharp/Iggy_SDK.Tests.BDD/Context/TestContext.cs
public class TestContext
{
public IIggyClient IggyClient { get; set; } = null!;
@@ -35,17 +30,3 @@ public class TestContext
public List<MessageResponse> PolledMessages { get; set; } = new();
public Message? LastSendMessage { get; set; }
}
-========
-public static class ProtocolHelpers
-{
- public static string GetWithProtocol(this string name, Protocol protocol)
- {
- return $"{name}_{protocol.ToString().ToLowerInvariant()}";
- }
-
- public static uint GetWithProtocol(this uint id, Protocol protocol)
- {
- return id + (uint)protocol * 10;
- }
-}
->>>>>>>>
master:foreign/csharp/Iggy_SDK.Tests.Integration/Helpers/NameIdHelpers.cs
diff --git
a/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs
b/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs
index a25a2202..c83b5fa9 100644
---
a/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs
+++
b/foreign/csharp/Iggy_SDK.Tests.BDD/StepDefinitions/BasicMessagingOperationsSteps.cs
@@ -70,10 +70,10 @@ public class BasicMessagingOperationsSteps
streams.Count.ShouldBe(0);
}
- [When(@"I create a stream with ID (\d+) and name (.*)")]
- public async Task WhenICreateAStreamWithIdAndName(uint streamId, string
streamName)
+ [When(@"I create a stream with name (.*)")]
+ public async Task WhenICreateAStreamWithName(string streamName)
{
- _context.CreatedStream = await
_context.IggyClient.CreateStreamAsync(streamName, streamId);
+ _context.CreatedStream = await
_context.IggyClient.CreateStreamAsync(streamName);
}
[Then(@"the stream should be created successfully")]
@@ -84,19 +84,17 @@ public class BasicMessagingOperationsSteps
_context.CreatedStream.Name.ShouldNotBeNullOrEmpty();
}
- [Then(@"the stream should have ID (\d+) and name (.*)")]
- public void ThenTheStreamShouldHaveIdAndName(uint expectedId, string
expectedName)
+ [Then(@"the stream should have name (.*)")]
+ public void ThenTheStreamShouldHaveName(string expectedName)
{
- _context.CreatedStream!.Id.ShouldBe(expectedId);
_context.CreatedStream!.Name.ShouldBe(expectedName);
}
- [When(@"I create a topic with ID (\d+) and name (.*) in stream (\d+) with
(\d+) partitions")]
- public async Task
WhenICreateATopicWithIdAndNameInStreamWithPartitions(uint topicId, string
topicName,
+ [When(@"I create a topic with name (.*) in stream (\d+) with (\d+)
partitions")]
+ public async Task WhenICreateATopicWithNameInStreamWithPartitions(string
topicName,
uint streamId, uint partitions)
{
_context.CreatedTopic = await
_context.IggyClient.CreateTopicAsync(Identifier.Numeric(streamId),
- topicId: topicId,
name: topicName,
partitionsCount: partitions);
}
@@ -109,10 +107,9 @@ public class BasicMessagingOperationsSteps
_context.CreatedTopic.Name.ShouldNotBeNullOrEmpty();
}
- [Then(@"the topic should have ID (\d+) and name (.*)")]
- public void ThenTheTopicShouldHaveIdAndName(uint expectedId, string
expectedName)
+ [Then(@"the topic should have name (.*)")]
+ public void ThenTheTopicShouldHaveName(string expectedName)
{
- _context.CreatedTopic!.Id.ShouldBe(expectedId);
_context.CreatedTopic!.Name.ShouldBe(expectedName);
}
diff --git a/foreign/python/src/client.rs b/foreign/python/src/client.rs
index 93af01ce..31f677e6 100644
--- a/foreign/python/src/client.rs
+++ b/foreign/python/src/client.rs
@@ -137,21 +137,20 @@ impl IggyClient {
/// Creates a new stream with the provided ID and name.
///
/// Returns Ok(()) on successful stream creation or a PyRuntimeError on
failure.
- #[pyo3(signature = (name, stream_id = None))]
-
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]",
imports=("collections.abc")))]
+ #[pyo3(signature = (name))]
+
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[StreamDetails]",
imports=("collections.abc")))]
fn create_stream<'a>(
&self,
py: Python<'a>,
name: String,
- stream_id: Option<u32>,
) -> PyResult<Bound<'a, PyAny>> {
let inner = self.inner.clone();
future_into_py(py, async move {
- inner
- .create_stream(&name, stream_id)
+ let stream = inner
+ .create_stream(&name)
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError,
_>(format!("{e:?}")))?;
- Ok(())
+ Ok(StreamDetails::from(stream))
})
}
@@ -178,12 +177,12 @@ impl IggyClient {
/// Creates a new topic with the given parameters.
///
- /// Returns Ok(()) on successful topic creation or a PyRuntimeError on
failure.
+ /// Returns TopicDetails on successful topic creation or a PyRuntimeError
on failure.
#[pyo3(
- signature = (stream, name, partitions_count, compression_algorithm =
None, topic_id = None, replication_factor = None)
+ signature = (stream, name, partitions_count, compression_algorithm =
None, replication_factor = None)
)]
#[allow(clippy::too_many_arguments)]
-
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[None]",
imports=("collections.abc")))]
+
#[gen_stub(override_return_type(type_repr="collections.abc.Awaitable[TopicDetails]",
imports=("collections.abc")))]
fn create_topic<'a>(
&self,
py: Python<'a>,
@@ -191,7 +190,6 @@ impl IggyClient {
name: String,
partitions_count: u32,
compression_algorithm: Option<String>,
- topic_id: Option<u32>,
replication_factor: Option<u8>,
) -> PyResult<Bound<'a, PyAny>> {
let compression_algorithm = match compression_algorithm {
@@ -204,20 +202,19 @@ impl IggyClient {
let inner = self.inner.clone();
future_into_py(py, async move {
- inner
+ let topic = inner
.create_topic(
&stream,
&name,
partitions_count,
compression_algorithm,
replication_factor,
- topic_id,
IggyExpiry::NeverExpire,
MaxTopicSize::ServerDefault,
)
.await
.map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError,
_>(format!("{e:?}")))?;
- Ok(())
+ Ok(TopicDetails::from(topic))
})
}
diff --git a/scripts/run-bdd-tests.sh b/scripts/run-bdd-tests.sh
index 45d334c9..58314c91 100755
--- a/scripts/run-bdd-tests.sh
+++ b/scripts/run-bdd-tests.sh
@@ -33,9 +33,24 @@ cleanup(){
}
trap cleanup EXIT INT TERM
+# Check if Docker supports cap_add
+check_docker_capabilities(){
+ if ! docker info | grep -q "Security Options"; then
+ log "โ ๏ธ Warning: Docker may not support security options"
+ fi
+
+ # Verify cap_add is supported by checking docker compose config
+ if ! docker compose config >/dev/null 2>&1; then
+ log "โ Error: docker-compose.yml validation failed"
+ exit 1
+ fi
+}
+
log "๐งช Running BDD tests for SDK: ${SDK}"
log "๐ Feature file: ${FEATURE}"
+check_docker_capabilities
+
run_suite(){
local svc="$1" emoji="$2" label="$3"
log "${emoji} ${label}โฆ"