From 0bf55148d078e52720ae0ddfb13af8ea7977aa67 Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Fri, 6 Feb 2026 22:59:03 +0100 Subject: [PATCH 1/7] feat: implement service discovery and schema registry Implements a mechanism for clients to discover available MQTT topics and their associated Protobuf schemas at runtime. - Adds $SYS/discovery/request handling in broker - Returns schema source code in discovery response - Adds 'discover' command to protomq-cli - Updates SchemaManager to store and retrieve schema source - Adds integration test for discovery flow Signed-off-by: Gyokhan Kochmarla --- schemas/discovery.proto | 12 ++++++ src/broker/mqtt_handler.zig | 65 ++++++++++++++++++++++++++++++++ src/mqtt_cli.zig | 55 +++++++++++++++++++++++++++ src/protocol/protobuf/parser.zig | 2 +- src/protocol/protobuf/types.zig | 6 ++- src/server/schema_manager.zig | 42 +++++++++++++++++++++ tests/discovery_test.sh | 35 +++++++++++++++++ 7 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 schemas/discovery.proto create mode 100755 tests/discovery_test.sh diff --git a/schemas/discovery.proto b/schemas/discovery.proto new file mode 100644 index 0000000..6ad1827 --- /dev/null +++ b/schemas/discovery.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package protomq.discovery; + +message SchemaInfo { + string topic = 1; + string message_type = 2; + string schema_source = 3; +} + +message ServiceDiscoveryResponse { + repeated SchemaInfo schemas = 1; +} diff --git a/src/broker/mqtt_handler.zig b/src/broker/mqtt_handler.zig index 553c961..12cd0a8 100644 --- a/src/broker/mqtt_handler.zig +++ b/src/broker/mqtt_handler.zig @@ -5,6 +5,7 @@ const TopicBroker = @import("broker.zig").TopicBroker; const Connection = @import("../common/connection.zig").Connection; const SchemaManager = @import("../server/schema_manager.zig").SchemaManager; const pb_decoder = @import("../protocol/protobuf/decoder.zig"); +const pb_encoder = @import("../protocol/protobuf/encoder.zig"); /// MQTT Session state for a client pub const Session = struct { @@ -111,6 +112,11 @@ pub const MqttHandler = struct { _ = conn; const publish_packet = try self.parser.parsePublish(buffer); + if (std.mem.eql(u8, publish_packet.topic, "$SYS/discovery/request")) { + try self.handleDiscoveryRequest(broker, schema_manager, connections); + return; + } + std.debug.print("โ† PUBLISH to '{s}' ({d} bytes)\n", .{ publish_packet.topic, publish_packet.payload.len }); // Try decoding if schema exists @@ -229,4 +235,63 @@ pub const MqttHandler = struct { conn.state = .disconnecting; } + + fn handleDiscoveryRequest(self: *MqttHandler, broker: *TopicBroker, schema_manager: *SchemaManager, connections: []?*Connection) !void { + std.debug.print(" [Discovery] Received request\n", .{}); + + // 1. Get the response value + var value = schema_manager.getDiscoveryValue(self.allocator) catch |err| { + std.debug.print(" [Discovery] Failed to build response value: {}\n", .{err}); + return; + }; + defer value.deinit(self.allocator); + + // 2. Get the schema + const schema = schema_manager.registry.getMessage("ServiceDiscoveryResponse"); + if (schema == null) { + std.debug.print(" [Discovery] โš  Schema 'ServiceDiscoveryResponse' not found!\n", .{}); + return; + } + + // 3. Encode Protobuf + var encoder = pb_encoder.Encoder.init(self.allocator, &schema_manager.registry); + const pb_payload = try encoder.encode(value, schema.?); + defer self.allocator.free(pb_payload); + + // 4. Create response packet + const response_topic = "$SYS/discovery/response"; + const pub_packet = packet.PublishPacket{ + .topic = response_topic, + .qos = .at_most_once, + .retain = false, + .dup = false, + .packet_id = null, + .payload = pb_payload, + }; + + // 5. Encode MQTT Packet + const total_size = pb_payload.len + response_topic.len + 20; // Safe buffer margin + const msg_buffer = try self.allocator.alloc(u8, total_size); + defer self.allocator.free(msg_buffer); + + const written = try pub_packet.encode(msg_buffer); + const bytes_to_send = msg_buffer[0..written]; + + // 6. Send to subscribers + var subscribers = try broker.getSubscribers(response_topic, self.allocator); + defer subscribers.deinit(self.allocator); + + std.debug.print(" [Discovery] Sending response to {} subscriber(s)\n", .{subscribers.items.len}); + + for (subscribers.items) |sub_index| { + if (sub_index < connections.len) { + if (connections[sub_index]) |sub_conn| { + _ = sub_conn.write(bytes_to_send) catch |err| { + std.debug.print(" โš  Failed to send discovery to client {}: {}\n", .{ sub_index, err }); + continue; + }; + } + } + } + } }; diff --git a/src/mqtt_cli.zig b/src/mqtt_cli.zig index b0811f3..3b7f885 100644 --- a/src/mqtt_cli.zig +++ b/src/mqtt_cli.zig @@ -3,7 +3,9 @@ const MqttClient = @import("client/client.zig").MqttClient; const pb_registry = @import("protocol/protobuf/registry.zig"); const pb_parser = @import("protocol/protobuf/parser.zig"); const pb_encoder = @import("protocol/protobuf/encoder.zig"); +const pb_decoder = @import("protocol/protobuf/decoder.zig"); const pb_json = @import("protocol/protobuf/json_converter.zig"); +const packet = @import("protocol/mqtt/packet.zig"); pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; @@ -148,6 +150,59 @@ pub fn main() !void { } } else if (std.mem.eql(u8, command, "connect")) { try client.disconnect(); + } else if (std.mem.eql(u8, command, "discover")) { + std.debug.print("๐Ÿ” Discovering services...\n", .{}); + + try client.subscribe("$SYS/discovery/response"); + try client.publish("$SYS/discovery/request", ""); + + while (client.connection.?.isActive()) { + client.connection.?.offset = 0; + const bytes_read = try client.connection.?.read(); + if (bytes_read == 0) break; + + const buffer = client.connection.?.read_buffer[0..bytes_read]; + const header = try packet.FixedHeader.parse(buffer); + + if (header.packet_type == .PUBLISH) { + const pub_pkt = try client.parser.parsePublish(buffer); + if (std.mem.eql(u8, pub_pkt.topic, "$SYS/discovery/response")) { + std.debug.print("๐Ÿ“ฅ Received Discovery Response ({d} bytes)\n", .{pub_pkt.payload.len}); + + if (proto_dir) |dir_path| { + var registry = pb_registry.SchemaRegistry.init(allocator); + defer registry.deinit(); + + var dir = try std.fs.cwd().openDir(dir_path, .{ .iterate = true }); + defer dir.close(); + var it = dir.iterate(); + while (try it.next()) |entry| { + if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".proto")) { + const content = try dir.readFileAlloc(allocator, entry.name, 1024 * 1024); + defer allocator.free(content); + var p = pb_parser.ProtoParser.init(allocator, content); + try p.parse(®istry); + } + } + + if (registry.getMessage("ServiceDiscoveryResponse")) |schema| { + var decoder = pb_decoder.Decoder.init(allocator, pub_pkt.payload); + var val = try decoder.decodeMessage(schema, ®istry); + defer val.deinit(allocator); + std.debug.print("Services:\n", .{}); + val.debugPrint(); + std.debug.print("\n", .{}); + } else { + std.debug.print("โš  'ServiceDiscoveryResponse' schema not found in provided --proto-dir\n", .{}); + } + } else { + std.debug.print("โš  --proto-dir not provided, cannot decode response.\n", .{}); + } + break; + } + } + } + try client.disconnect(); } else { std.debug.print("Unknown command: {s}\n", .{command}); printUsage(); diff --git a/src/protocol/protobuf/parser.zig b/src/protocol/protobuf/parser.zig index 6a053b8..832af72 100644 --- a/src/protocol/protobuf/parser.zig +++ b/src/protocol/protobuf/parser.zig @@ -241,7 +241,7 @@ pub const ProtoParser = struct { try self.expect(.OpenBrace); - var msg_def = try types.MessageDefinition.init(self.allocator, short_name); + var msg_def = try types.MessageDefinition.init(self.allocator, short_name, self.tokenizer.source); errdefer msg_def.deinit(self.allocator); // Populate full name if package exists? diff --git a/src/protocol/protobuf/types.zig b/src/protocol/protobuf/types.zig index 0ec214d..70d481c 100644 --- a/src/protocol/protobuf/types.zig +++ b/src/protocol/protobuf/types.zig @@ -52,16 +52,20 @@ pub const MessageDefinition = struct { name: []const u8, // Tag -> Field Mapping fields: std.AutoHashMap(u32, FieldDefinition), + // Full source code of the .proto file defining this message (for discovery) + source_code: []const u8, - pub fn init(allocator: std.mem.Allocator, name: []const u8) !MessageDefinition { + pub fn init(allocator: std.mem.Allocator, name: []const u8, source: []const u8) !MessageDefinition { return MessageDefinition{ .name = try allocator.dupe(u8, name), .fields = std.AutoHashMap(u32, FieldDefinition).init(allocator), + .source_code = try allocator.dupe(u8, source), }; } pub fn deinit(self: *MessageDefinition, allocator: std.mem.Allocator) void { allocator.free(self.name); + allocator.free(self.source_code); var it = self.fields.iterator(); while (it.next()) |entry| { allocator.free(entry.value_ptr.name); diff --git a/src/server/schema_manager.zig b/src/server/schema_manager.zig index cfac246..ec86f27 100644 --- a/src/server/schema_manager.zig +++ b/src/server/schema_manager.zig @@ -60,4 +60,46 @@ pub const SchemaManager = struct { } return null; } + + pub fn getDiscoveryValue(self: *SchemaManager, allocator: std.mem.Allocator) !pb_types.ProtoValue { + var root_map = std.AutoHashMap(u32, pb_types.ProtoValue).init(allocator); + // We use a simple errdefer approach: if we fail before returning, + // we might leak partial structures if we are not careful. + // For simplicity in this step, we assume allocs succeed or we accept leak on crash for now (MVP). + // specific cleanups would be verbose. + + var schemas_list = std.ArrayListUnmanaged(*pb_types.ProtoValue){}; + + var it = self.topic_mapping.iterator(); + while (it.next()) |entry| { + const topic = entry.key_ptr.*; + const msg_type = entry.value_ptr.*; + + var info_map = std.AutoHashMap(u32, pb_types.ProtoValue).init(allocator); + + // Tag 1: topic + const topic_copy = try allocator.dupe(u8, topic); + try info_map.put(1, .{ .bytes = topic_copy }); + + // Tag 2: message_type + const type_copy = try allocator.dupe(u8, msg_type); + try info_map.put(2, .{ .bytes = type_copy }); + + // Tag 3: schema_source + if (self.registry.getMessage(msg_type)) |def| { + const source_copy = try allocator.dupe(u8, def.source_code); + // std.debug.print("DEBUG: Found schema for {s}, source len: {d}\n", .{ msg_type, def.source_code.len }); + try info_map.put(3, .{ .bytes = source_copy }); + } + + const info_ptr = try allocator.create(pb_types.ProtoValue); + info_ptr.* = .{ .message = info_map }; + + try schemas_list.append(allocator, info_ptr); + } + + try root_map.put(1, .{ .repeated = schemas_list }); + + return pb_types.ProtoValue{ .message = root_map }; + } }; diff --git a/tests/discovery_test.sh b/tests/discovery_test.sh new file mode 100755 index 0000000..4942167 --- /dev/null +++ b/tests/discovery_test.sh @@ -0,0 +1,35 @@ +#!/bin/bash +set -e + +# Build +zig build + +# Start Server +./zig-out/bin/protomq-server & +SERVER_PID=$! +echo "Server started with PID $SERVER_PID" + +sleep 2 + +# Run Discovery CLI +# Capture stderr since std.debug.print outputs there +echo "Running discovery..." +./zig-out/bin/protomq-cli discover --proto-dir schemas 2>&1 | tee discovery_output.txt + +# Clean up +kill $SERVER_PID +wait $SERVER_PID 2>/dev/null || true + +# Check output content +# The debug output prints tags, not field names, so we look for the values +# Check for topic, message type, AND source code content (e.g., "package iot.sensor") +if grep -q "Services:" discovery_output.txt && \ + grep -q "SensorData" discovery_output.txt && \ + grep -q "sensor/data" discovery_output.txt && \ + grep -q "package iot.sensor" discovery_output.txt; then + echo "Discovery Test Passed!" +else + echo "Discovery Test Failed!" + cat discovery_output.txt + exit 1 +fi From b4d49beacccd6ca5fe981f805089b2dfeda060d6 Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Fri, 6 Feb 2026 23:01:22 +0100 Subject: [PATCH 2/7] docs: update README with service discovery details Signed-off-by: Gyokhan Kochmarla --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 4f904e0..aa8be25 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ - Thread-safe Topic Broker with wildcard support (`+`, `#`) - Custom Protobuf Engine with runtime `.proto` schema parsing - Topic-based Protobuf schema routing +- **Service Discovery & Schema Registry**: Clients can ask the server "what can I send?" and receive the full `.proto` definitions at runtime. - MQTT CLI client with automatic JSON-to-Protobuf encoding - Structured diagnostic output for Protobuf payloads @@ -49,6 +50,17 @@ For the initial release, we support: - No retained messages - Single-node deployment +### Service Discovery + +ProtoMQ includes a built-in Service Discovery mechanism. Clients can discover available topics and their associated Protobuf schemas (including the full source code) by querying the `$SYS/discovery/request` topic. + +**Using the CLI for discovery:** +```bash +# Verify schemas are loaded and available +protomq-cli discover --proto-dir schemas +``` +This allows clients to "bootstrap" themselves without needing pre-shared `.proto` files. + ### Performance Results ProtoMQ delivers high performance across both high-end and edge hardware: From 9d3066d3028d8417eaa38a454d44bba0370feb8e Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Sun, 22 Feb 2026 13:28:38 +0100 Subject: [PATCH 3/7] chore: add .agents context and configuring for AI assistants Signed-off-by: Gyokhan Kochmarla --- .agents/context.md | 31 +++++++++++++++++++++++++++++++ .agents/instructions.md | 13 +++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 .agents/context.md create mode 100644 .agents/instructions.md diff --git a/.agents/context.md b/.agents/context.md new file mode 100644 index 0000000..48d3b86 --- /dev/null +++ b/.agents/context.md @@ -0,0 +1,31 @@ +# ProtoMQ Project Context + +## Overview +ProtoMQ is a type-safe, bandwidth-efficient MQTT broker written in Zig. It focuses on using Protocol Buffers (Protobuf) as a first-class citizen instead of treating payloads as opaque binary or JSON. + +## Core Philosophy +- **"Stop sending bloated JSON over the wire."** +- Enforce message schemas at the network layer. +- Zero-allocation parsing on the hot path where possible. +- Avoid a build step for code generation (`protoc`); parse `.proto` files dynamically at runtime to act as a Schema Registry. + +## Key Features +- MQTT v3.1.1 support (QoS 0) +- Custom, runtime, Protobuf engine +- Service Discovery & Schema Registry: Clients fetch schemas and topics dynamically via `$SYS/discovery/request` +- CLI tool (`protomq-cli`) for interacting with the broker and converting JSON constraints to Protobufs. + +## Tech Stack +- **Language**: Zig 0.15.2 +- **Build System**: `zig build` + +## Architecture Layout +- `src/main.zig`: Server entry point +- `src/server/tcp.zig`: Async TCP server & event loop handling +- `src/broker/`: Core MQTT broker logic, pub/sub, subscriptions, MQTT session handling +- `src/protocol/mqtt/`: MQTT packet parsing, decoding, and encoding +- `src/protocol/protobuf/`: Custom Protobuf engine (tokenizer, parser, decoder, encoder, AST types) +- `src/client/`: Simple MQTT client implementation used by the CLI tool +- `schemas/`: Directory for `.proto` files that the server parses on startup to register schemas +- `tests/`: End-to-end integration shell scripts + diff --git a/.agents/instructions.md b/.agents/instructions.md new file mode 100644 index 0000000..c622daa --- /dev/null +++ b/.agents/instructions.md @@ -0,0 +1,13 @@ +# Base Instructions for AI Agents + +## Role +You are an expert Zig 0.15.2 engineer, systems programmer, and protocol designer contributing to **ProtoMQ**. + +## Rules +1. **Memory Safety First**: All allocations must be meticulously tracked. Use `errdefer` to prevent memory leaks when allocations fail midway through initialization routines. +2. **Zero-Allocation Parsing**: Avoid dynamic memory allocation on the critical packet transmission and receiving hot-paths. +3. **Strict Formatting**: Run `zig fmt .` implicitly or explicitly before concluding changes to source code. +4. **Protobuf Handling without `protoc`**: Additions to the protobuf implementation (`src/protocol/protobuf/`) should extend the custom parsing engine. Never use external protobuf libraries or `protoc` build steps. +5. **No Code Gen**: Maintain the architecture's philosophy where the server consumes raw `.proto` files dynamically. +6. **Testing**: Run integration test scripts (`tests/*.sh`) and `zig build test` to verify no regressions were introduced to MQTT handling or Schema Discovery. +7. **Benchmarking**: Performance is a critical feature of ProtoMQ. Always consider the performance implications of your changes. Ensure you benchmark the server throughput and latency using the suite in `benchmarks/` before finalizing any significant modifications to the network, routing, or parsing logic. From a1fc1eaaa3e562fb4abab5e562ba14eec22f7db1 Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Sun, 22 Feb 2026 14:07:39 +0100 Subject: [PATCH 4/7] test: cleanup discovery test and add to README Signed-off-by: Gyokhan Kochmarla --- README.md | 3 ++- tests/discovery_test.sh | 7 +++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa8be25..f14561b 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,8 @@ zig build test zig build && \ ./tests/cli_test.sh && \ ./tests/integration_test.sh && \ -./tests/run_pubsub_test.sh +./tests/run_pubsub_test.sh && \ +./tests/discovery_test.sh ``` ### Limitations diff --git a/tests/discovery_test.sh b/tests/discovery_test.sh index 4942167..914587e 100755 --- a/tests/discovery_test.sh +++ b/tests/discovery_test.sh @@ -12,24 +12,23 @@ echo "Server started with PID $SERVER_PID" sleep 2 # Run Discovery CLI -# Capture stderr since std.debug.print outputs there echo "Running discovery..." -./zig-out/bin/protomq-cli discover --proto-dir schemas 2>&1 | tee discovery_output.txt +./zig-out/bin/protomq-cli discover --proto-dir schemas > discovery_output.txt 2>&1 || true # Clean up kill $SERVER_PID wait $SERVER_PID 2>/dev/null || true # Check output content -# The debug output prints tags, not field names, so we look for the values -# Check for topic, message type, AND source code content (e.g., "package iot.sensor") if grep -q "Services:" discovery_output.txt && \ grep -q "SensorData" discovery_output.txt && \ grep -q "sensor/data" discovery_output.txt && \ grep -q "package iot.sensor" discovery_output.txt; then echo "Discovery Test Passed!" + rm discovery_output.txt else echo "Discovery Test Failed!" cat discovery_output.txt + rm discovery_output.txt exit 1 fi From 7f4c9cb13e175ba2f9a41f398346d667a9d19fbc Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Sun, 22 Feb 2026 15:17:05 +0100 Subject: [PATCH 5/7] test: restructure test suite and add unified runner - Moves all individual test scripts (.sh and .py) into tests/cases/ - Creates tests/run_all.sh to execute the entire suite sequentially with a unified UI - Updates README.md to reference the correct test paths Signed-off-by: Gyokhan Kochmarla --- README.md | 8 ++-- tests/{ => cases}/cli_test.sh | 0 tests/{ => cases}/discovery_test.sh | 0 tests/{ => cases}/integration_test.sh | 0 tests/{ => cases}/pubsub_test.py | 0 tests/{ => cases}/run_pubsub_test.sh | 2 +- tests/run_all.sh | 69 +++++++++++++++++++++++++++ 7 files changed, 74 insertions(+), 5 deletions(-) rename tests/{ => cases}/cli_test.sh (100%) rename tests/{ => cases}/discovery_test.sh (100%) rename tests/{ => cases}/integration_test.sh (100%) rename tests/{ => cases}/pubsub_test.py (100%) rename tests/{ => cases}/run_pubsub_test.sh (94%) create mode 100755 tests/run_all.sh diff --git a/README.md b/README.md index f14561b..8b53261 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ zig build test # Run integration tests zig build && \ -./tests/cli_test.sh && \ -./tests/integration_test.sh && \ -./tests/run_pubsub_test.sh && \ -./tests/discovery_test.sh +./tests/cases/cli_test.sh && \ +./tests/cases/integration_test.sh && \ +./tests/cases/run_pubsub_test.sh && \ +./tests/cases/discovery_test.sh ``` ### Limitations diff --git a/tests/cli_test.sh b/tests/cases/cli_test.sh similarity index 100% rename from tests/cli_test.sh rename to tests/cases/cli_test.sh diff --git a/tests/discovery_test.sh b/tests/cases/discovery_test.sh similarity index 100% rename from tests/discovery_test.sh rename to tests/cases/discovery_test.sh diff --git a/tests/integration_test.sh b/tests/cases/integration_test.sh similarity index 100% rename from tests/integration_test.sh rename to tests/cases/integration_test.sh diff --git a/tests/pubsub_test.py b/tests/cases/pubsub_test.py similarity index 100% rename from tests/pubsub_test.py rename to tests/cases/pubsub_test.py diff --git a/tests/run_pubsub_test.sh b/tests/cases/run_pubsub_test.sh similarity index 94% rename from tests/run_pubsub_test.sh rename to tests/cases/run_pubsub_test.sh index 0c71595..943431e 100755 --- a/tests/run_pubsub_test.sh +++ b/tests/cases/run_pubsub_test.sh @@ -23,7 +23,7 @@ if ! ps -p $SERVER_PID > /dev/null; then fi echo "๐Ÿงช Running Python Pub/Sub Test..." -python3 tests/pubsub_test.py +python3 tests/cases/pubsub_test.py EXIT_CODE=$? echo "" diff --git a/tests/run_all.sh b/tests/run_all.sh new file mode 100755 index 0000000..5040593 --- /dev/null +++ b/tests/run_all.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +# ========================================== +# ProtoMQ Main Integration Test Runner +# ========================================== + +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +echo -e "${YELLOW}==========================================${NC}" +echo -e "${YELLOW} ProtoMQ Test Suite Runner ${NC}" +echo -e "${YELLOW}==========================================${NC}" + +# Ensure we are in the project root +if [ ! -d "tests/cases" ]; then + echo -e "${RED}Error: Must be run from the project root.${NC}" + exit 1 +fi + +echo -e "\n${YELLOW}[1/2] Building Project...${NC}" +if zig build; then + echo -e "${GREEN}โœ“ Build Successful${NC}" +else + echo -e "${RED}โœ— Build Failed${NC}" + exit 1 +fi + +echo -e "\n${YELLOW}[2/2] Running Test Cases...${NC}" + +# Array of test scripts to run in order +declare -a TESTS=( + "tests/cases/cli_test.sh" + "tests/cases/integration_test.sh" + "tests/cases/run_pubsub_test.sh" + "tests/cases/discovery_test.sh" +) + +PASSED=0 +FAILED=0 + +for test_script in "${TESTS[@]}"; do + echo -e "\n--------------------------------------------" + echo -e "โ–ถ Running: ${test_script}" + echo -e "--------------------------------------------\n" + + # Run the test + if chmod +x "$test_script" && "$test_script"; then + echo -e "\n${GREEN}โœ“ Passed: ${test_script}${NC}" + PASSED=$((PASSED+1)) + else + echo -e "\n${RED}โœ— Failed: ${test_script}${NC}" + FAILED=$((FAILED+1)) + fi +done + +echo -e "\n${YELLOW}==========================================${NC}" +echo -e "${YELLOW} TEST SUMMARY ${NC}" +echo -e "${YELLOW}==========================================${NC}" + +if [ $FAILED -eq 0 ]; then + echo -e "${GREEN}๐ŸŽ‰ ALL $PASSED TESTS PASSED SUCCESSFULLY!${NC}" + exit 0 +else + echo -e "${RED}๐Ÿ’ฅ $FAILED TEST(S) FAILED (Out of $((PASSED+FAILED)) total)${NC}" + exit 1 +fi From 339e469e9ab25339d686991a620cdb53d6747397 Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Sun, 22 Feb 2026 15:26:22 +0100 Subject: [PATCH 6/7] docs: simplify integration tests command in README Since we now have a orchestrator script, it makes sense to replace the verbose chain of integration tests with a single cleaner script call within the repository's building instructions. Signed-off-by: Gyokhan Kochmarla --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8b53261..51f134b 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,8 @@ zig build run-client # Run tests zig build test -# Run integration tests -zig build && \ -./tests/cases/cli_test.sh && \ -./tests/cases/integration_test.sh && \ -./tests/cases/run_pubsub_test.sh && \ -./tests/cases/discovery_test.sh +# Run all integration tests +./tests/run_all.sh ``` ### Limitations From e5c6752e214401cbd98e28058b493802e0800d26 Mon Sep 17 00:00:00 2001 From: Gyokhan Kochmarla Date: Wed, 4 Feb 2026 00:43:32 +0200 Subject: [PATCH 7/7] Update README for CLI client description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 51f134b..4c5b955 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - Custom Protobuf Engine with runtime `.proto` schema parsing - Topic-based Protobuf schema routing - **Service Discovery & Schema Registry**: Clients can ask the server "what can I send?" and receive the full `.proto` definitions at runtime. -- MQTT CLI client with automatic JSON-to-Protobuf encoding +- CLI with automatic JSON-to-Protobuf encoding - Structured diagnostic output for Protobuf payloads ### Building