diff --git a/core/include/gnuradio-4.0/Port.hpp b/core/include/gnuradio-4.0/Port.hpp index 96ac0d0d..2dd30327 100644 --- a/core/include/gnuradio-4.0/Port.hpp +++ b/core/include/gnuradio-4.0/Port.hpp @@ -754,7 +754,7 @@ struct Port { template inline constexpr void processPublishTag(PropertyMap &&tag_data, Tag::signed_index_type tagOffset) noexcept requires(kIsOutput) { - const auto newTagIndex = tagOffset < 0 ? tagOffset : streamWriter().position() + tagOffset; + const auto newTagIndex = tagOffset < 0 ? tagOffset : streamWriter().position() + 1 + tagOffset; fmt::print("publishing tag at {}, stream writer already published {} samples\n", newTagIndex, streamWriter().nSamplesPublished()); if (isConnected() && tagOffset >= 0 && (_cachedTag.index != newTagIndex && _cachedTag.index != -1)) { // do not cache tags that have an explicit index diff --git a/core/test/CMakeLists.txt b/core/test/CMakeLists.txt index 44a1cafa..19badd35 100644 --- a/core/test/CMakeLists.txt +++ b/core/test/CMakeLists.txt @@ -36,6 +36,7 @@ add_ut_test(qa_DynamicPort) add_ut_test(qa_HierBlock) add_ut_test(qa_Block) add_ut_test(qa_LifeCycle) +add_ut_test(qa_Port) add_ut_test(qa_Scheduler) add_ut_test(qa_reader_writer_lock) add_ut_test(qa_Settings) diff --git a/core/test/qa_Port.cpp b/core/test/qa_Port.cpp new file mode 100644 index 00000000..198a430b --- /dev/null +++ b/core/test/qa_Port.cpp @@ -0,0 +1,114 @@ +#include + +#include +#include + +#include + +template<> +struct fmt::formatter { + template + constexpr auto + parse(ParseContext &ctx) { + return ctx.begin(); + } + + template + constexpr auto + format(const gr::Tag &tag, FormatContext &ctx) const { + return fmt::format_to(ctx.out(), " {}->{{ {} }}\n", tag.index, tag.map); + } +}; + +const boost::ut::suite PortTests = [] { + using namespace boost::ut; + using namespace gr; + + "CustomSizePort"_test = [] { + PortOut, StreamBufferType>> out; + expect(out.resizeBuffer(32) == gr::ConnectionResult::SUCCESS); + expect(eq(out.buffer().streamBuffer.size(), static_cast(getpagesize()))); // 4096 (page-size) is the minimum buffer size + }; + + "InputPort"_test = [] { + PortIn in; + expect(eq(in.buffer().streamBuffer.size(), 65536UZ)); + + auto writer = in.buffer().streamBuffer.new_writer(); + auto tagWriter = in.buffer().tagBuffer.new_writer(); + { // put testdata into buffer + auto writeSpan = writer.reserve(5); + auto tagSpan = tagWriter.reserve(3); + tagSpan[0] = {-1, {{"id", "tag@-1"}}}; + tagSpan[1] = {1, {{"id", "tag@101"}}}; + tagSpan[2] = {4, {{"id", "tag@104"}}}; + std::ranges::iota(writeSpan, 100); + tagSpan.publish(3); // this should not be necessary as the ProcessAll policy should publish automatically + writeSpan.publish(5); // this should not be necessary as the ProcessAll policy should publish automatically + } + { // partial consume + auto data = in.get(5); + expect(std::ranges::equal(data.tags, std::vector{ {-1, {{"id", "tag@-1"}}}, {1, {{"id", "tag@101"}}}, {4, {{"id", "tag@104"}}} })); + expect(std::ranges::equal(data, std::views::iota(100) | std::views::take(5))); + expect(data.consume(2)); + } + { // full consume + auto data = in.get(2); + expect(std::ranges::equal(data.tags, std::vector{ {1, {{"id", "tag@101"}}}} )); + expect(std::ranges::equal(data, std::views::iota(100) | std::views::drop(2) | std::views::take(2))); + } + { // get empty range + auto data = in.get(0); + expect(std::ranges::equal(data.tags, std::vector{} )); + expect(std::ranges::equal(data, std::vector())); + } + }; + + "OutputPort"_test = [] { + PortOut out; + auto reader = out.buffer().streamBuffer.new_reader(); + auto tagReader = out.buffer().tagBuffer.new_reader(); + { + auto data = out.reserve(5); + out.publishTag({{"id", "tag@-1"}}, -1); + out.publishPendingTags(); + out.publishTag({{"id", "tag@101"}}, 1); + out.publishPendingTags(); + out.publishTag({{"id", "tag@104"}}, 4); + out.publishPendingTags(); + std::ranges::iota(data, 100); + out.publishPendingTags(); + data.publish(5); // should be automatic + } + { + auto data = reader.get(); + auto tags = tagReader.get(); + expect(std::ranges::equal(data, std::views::iota(100) | std::views::take(5))); + // fmt::print("data: {}, tags: {}\n", std::span{data}, std::span{tags}); + expect(std::ranges::equal(tags, std::vector{ { -1, {{"id", "tag@-1"}}}, {1, {{"id", "tag@101"}}}, {4, {{"id", "tag@104"}}} })); + } + { + auto data = out.reserve(5); + out.publishTag({{"id", "tag@-1"}}, -1); + out.publishPendingTags(); + out.publishTag({{"id", "tag@106"}}, 1); + out.publishPendingTags(); + out.publishTag({{"id", "tag@109"}}, 4); + out.publishPendingTags(); + std::ranges::iota(data, 105); + out.publishPendingTags(); + data.publish(5); // should be automatic + } + { + auto data = reader.get(); + auto tags = tagReader.get(); + expect(std::ranges::equal(data, std::views::iota(105) | std::views::take(5))); + //fmt::print("data: {}, tags: {}\n", std::span{data}, std::span{tags}); + expect(std::ranges::equal(tags, std::vector{ { -1, {{"id", "tag@-1"}}}, {6, {{"id", "tag@106"}}}, {9, {{"id", "tag@109"}}} })); + } + }; +}; + +int +main() { /* tests are statically executed */ +}