Skip to content

Commit

Permalink
added checkBlockContracts<TBlock>() checker
Browse files Browse the repository at this point in the history
Block<T>() default constructor checks, throws, and provides first-order remedies for:
 * unsupported settings type
 * missing or wrong process[One, Bulk] signatures
 * added deprecation warning if raw `work(...)` is being implemented
 * fixed emscripten conversion error - size_t-like types are now mapped to std::uint32_t <-> gr::Size_t

added unit-tests to specifically test the settings and signatures

N.B. the process[One, Bulk] API refactoring w.r.t. the ConsumableSpan and PublishableSpan concepts and implementations is of out scope for the commit.

Signed-off-by: Ralph J. Steinhagen <[email protected]>
  • Loading branch information
RalphSteinhagen authored and drslebedev committed Feb 13, 2024
1 parent fdb41b7 commit f00b458
Show file tree
Hide file tree
Showing 24 changed files with 606 additions and 235 deletions.
16 changes: 8 additions & 8 deletions algorithm/test/qa_algorithm_fourier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,11 @@ const boost::ut::suite<"FFT algorithms and window functions"> windowTests = [] {
typename T::AlgoType fftAlgo{};
constexpr double tolerance{ 1.e-5 };
struct TestParams {
std::uint32_t N{ 1024 }; // must be power of 2
double sample_rate{ 128. }; // must be power of 2 (only for the unit test for easy comparison with true result)
double frequency{ 1. };
double amplitude{ 1. };
bool outputInDb{ false };
gr::Size_t N{ 1024 }; // must be power of 2
double sample_rate{ 128. }; // must be power of 2 (only for the unit test for easy comparison with true result)
double frequency{ 1. };
double amplitude{ 1. };
bool outputInDb{ false };
};

std::vector<TestParams> testCases = { { 256, 128., 10., 5., false }, { 512, 4., 1., 1., false }, { 512, 32., 1., 0.1, false }, { 256, 128., 10., 5., false } };
Expand All @@ -115,9 +115,9 @@ const boost::ut::suite<"FFT algorithms and window functions"> windowTests = [] {

"FFT algo pattern tests"_test = []<typename T>() {
using InType = T::InType;
typename T::AlgoType fftAlgo{};
constexpr double tolerance{ 1.e-5 };
constexpr std::uint32_t N{ 16 };
typename T::AlgoType fftAlgo{};
constexpr double tolerance{ 1.e-5 };
constexpr gr::Size_t N{ 16 };
static_assert(N == 16, "expected values are calculated for N == 16");

std::vector<InType> signal(N);
Expand Down
27 changes: 13 additions & 14 deletions blocks/basic/include/gnuradio-4.0/basic/Selector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
namespace gr::basic {
using namespace gr;



using SelectorDoc = Doc<R""(
@brief basic multiplexing class to route arbitrary inputs to outputs
Expand Down Expand Up @@ -69,25 +67,26 @@ struct Selector : Block<Selector<T>, SelectorDoc> {
using A = Annotated<U, description, Arguments...>;

// port definitions
PortIn<std::uint32_t, Async, Optional> selectOut;
PortOut<T, Async, Optional> monitorOut; // optional monitor output (for diagnostics and debugging purposes)
std::vector<PortIn<T, Async>> inputs; // TODO: need to add exception to pmt_t that this isn't interpreted as a settings type
std::vector<PortOut<T, Async>> outputs;
PortIn<gr::Size_t, Async, Optional> selectOut;
PortOut<T, Async, Optional> monitorOut; // optional monitor output (for diagnostics and debugging purposes)
std::vector<PortIn<T, Async>> inputs; // TODO: need to add exception to pmt_t that this isn't interpreted as a settings type
std::vector<PortOut<T, Async>> outputs;

// settings
A<std::uint32_t, "n_inputs", Visible, Doc<"variable number of inputs">, Limits<1U, 32U>> n_inputs = 0U;
A<std::uint32_t, "n_outputs", Visible, Doc<"variable number of inputs">, Limits<1U, 32U>> n_outputs = 0U;
A<std::vector<std::uint32_t>, "map_in", Visible, Doc<"input port index to route from">> map_in; // N.B. need two vectors since pmt_t doesn't support pairs (yet!?!)
A<std::vector<std::uint32_t>, "map_out", Visible, Doc<"output port index to route to">> map_out;
A<gr::Size_t, "n_inputs", Visible, Doc<"variable number of inputs">, Limits<1U, 32U>> n_inputs = 0U;
A<gr::Size_t, "n_outputs", Visible, Doc<"variable number of inputs">, Limits<1U, 32U>> n_outputs = 0U;
A<std::vector<gr::Size_t>, "map_in", Visible, Doc<"input port index to route from">> map_in; // N.B. need two vectors since pmt_t doesn't support pairs (yet!?!)
A<std::vector<gr::Size_t>, "map_out", Visible, Doc<"output port index to route to">> map_out;
A<bool, "back_pressure", Visible, Doc<"true: do not consume samples from un-routed ports">> back_pressure = false;
std::map<std::uint32_t, std::vector<std::uint32_t>> _internalMapping;
std::uint32_t _selectedSrc = -1U;
std::map<gr::Size_t, std::vector<gr::Size_t>> _internalMapping;
gr::Size_t _selectedSrc = -1U;

void
settingsChanged(const gr::property_map &old_settings, const gr::property_map &new_settings) {
if (new_settings.contains("n_inputs") || new_settings.contains("n_outputs")) {
fmt::print("{}: configuration changed: n_inputs {} -> {}, n_outputs {} -> {}\n", static_cast<void *>(this), old_settings.at("n_inputs"),
new_settings.contains("n_inputs") ? new_settings.at("n_inputs") : "same", old_settings.at("n_outputs"), new_settings.contains("n_outputs") ? new_settings.at("n_outputs") : "same");
new_settings.contains("n_inputs") ? new_settings.at("n_inputs") : "same", old_settings.at("n_outputs"),
new_settings.contains("n_outputs") ? new_settings.at("n_outputs") : "same");
inputs.resize(n_inputs);
outputs.resize(n_outputs);
}
Expand All @@ -105,7 +104,7 @@ struct Selector : Block<Selector<T>, SelectorDoc> {
}
}

using select_reader_t = typename PortIn<std::uint32_t, Async, Optional>::ReaderType;
using select_reader_t = typename PortIn<gr::Size_t, Async, Optional>::ReaderType;
using monitor_writer_t = typename PortOut<T, Async, Optional>::WriterType;
using input_reader_t = typename PortIn<T, Async>::ReaderType;
using output_writer_t = typename PortOut<T, Async>::WriterType;
Expand Down
22 changes: 11 additions & 11 deletions blocks/basic/include/gnuradio-4.0/basic/clock_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ struct ClockSource : public gr::Block<ClockSource<T, useIoThread, ClockSourceTyp
std::uint64_t repeat_period{ 0 }; // if repeat_period > last tag_time -> restart tags, in nanoseconds
bool do_zero_order_hold{ false }; // if more tag_times than values: true=publish last tag, false=publish empty

A<std::uint32_t, "n_samples_max", Visible, Doc<"0: unlimited">> n_samples_max = 1024;
std::uint32_t n_samples_produced{ 0 };
A<float, "avg. sample rate", Visible> sample_rate = 1000.f;
A<std::uint32_t, "chunk_size", Visible, Doc<"number of samples per update">> chunk_size = 100;
std::shared_ptr<std::thread> userProvidedThread;
bool verbose_console = false;
A<gr::Size_t, "n_samples_max", Visible, Doc<"0: unlimited">> n_samples_max = 1024;
gr::Size_t n_samples_produced{ 0 };
A<float, "avg. sample rate", Visible> sample_rate = 1000.f;
A<gr::Size_t, "chunk_size", Visible, Doc<"number of samples per update">> chunk_size = 100;
std::shared_ptr<std::thread> userProvidedThread;
bool verbose_console = false;

private:
std::chrono::time_point<ClockSourceType> _beginSequenceTimePoint = ClockSourceType::now();
Expand Down Expand Up @@ -109,10 +109,10 @@ struct ClockSource : public gr::Block<ClockSource<T, useIoThread, ClockSourceTyp
std::this_thread::sleep_until(nextTimePoint);
}

const std::uint32_t remainingSamples = n_samples_max - n_samples_produced;
std::uint32_t samplesToProduce = std::min(remainingSamples, chunk_size.value);
const gr::Size_t remainingSamples = n_samples_max - n_samples_produced;
gr::Size_t samplesToProduce = std::min(remainingSamples, chunk_size.value);

std::uint32_t samplesToNextTimeTag = std::numeric_limits<uint32_t>::max();
gr::Size_t samplesToNextTimeTag = std::numeric_limits<uint32_t>::max();
if (!tag_times.empty()) {
if (next_time_tag == 0 && !_beginSequenceTimePointInitialized) {
_beginSequenceTimePoint = nextTimePoint;
Expand All @@ -126,11 +126,11 @@ struct ClockSource : public gr::Block<ClockSource<T, useIoThread, ClockSourceTyp
if (next_time_tag < tag_times.size()) {
const auto currentTagTime = std::chrono::microseconds(tag_times[next_time_tag] / 1000); // ns -> μs
const auto timeToNextTag = std::chrono::duration_cast<std::chrono::microseconds>((_beginSequenceTimePoint + currentTagTime - nextTimePoint));
samplesToNextTimeTag = static_cast<std::uint32_t>((static_cast<double>(timeToNextTag.count()) / 1.e6) * static_cast<double>(sample_rate));
samplesToNextTimeTag = static_cast<gr::Size_t>((static_cast<double>(timeToNextTag.count()) / 1.e6) * static_cast<double>(sample_rate));
}
}

auto samplesToNextTag = tags.empty() || next_tag >= tags.size() ? std::numeric_limits<uint32_t>::max() : static_cast<std::uint32_t>(tags[next_tag].index) - n_samples_produced;
auto samplesToNextTag = tags.empty() || next_tag >= tags.size() ? std::numeric_limits<uint32_t>::max() : static_cast<gr::Size_t>(tags[next_tag].index) - n_samples_produced;

if (samplesToNextTag < samplesToNextTimeTag) {
if (next_tag < tags.size() && samplesToNextTag <= samplesToProduce) {
Expand Down
26 changes: 13 additions & 13 deletions blocks/basic/test/qa_DataSink.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,8 @@ const boost::ut::suite DataSinkTests = [] {
using namespace std::string_literals;

"callback continuous mode"_test = [] {
constexpr std::uint64_t kSamples = 200005;
constexpr std::size_t kChunkSize = 1000;
constexpr gr::Size_t kSamples = 200005;
constexpr std::size_t kChunkSize = 1000;

const auto srcTags = makeTestTags(0, 1000);

Expand Down Expand Up @@ -262,7 +262,7 @@ const boost::ut::suite DataSinkTests = [] {
};

"blocking polling continuous mode"_test = [] {
constexpr std::uint64_t kSamples = 200000;
constexpr gr::Size_t kSamples = 200000;

gr::Graph testGraph;
const auto tags = makeTestTags(0, 1000);
Expand Down Expand Up @@ -339,7 +339,7 @@ const boost::ut::suite DataSinkTests = [] {
};

"blocking polling trigger mode non-overlapping"_test = [] {
constexpr std::uint64_t kSamples = 200000;
constexpr gr::Size_t kSamples = 200000;

gr::Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::testing::TagSource<int32_t>>({ { "n_samples_max", kSamples }, { "mark_tag", false } });
Expand Down Expand Up @@ -401,7 +401,7 @@ const boost::ut::suite DataSinkTests = [] {
};

"blocking snapshot mode"_test = [] {
constexpr std::uint64_t kSamples = 200000;
constexpr gr::Size_t kSamples = 200000;

gr::Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::testing::TagSource<int32_t>>({ { "n_samples_max", kSamples }, { "mark_tag", false } });
Expand Down Expand Up @@ -472,11 +472,11 @@ const boost::ut::suite DataSinkTests = [] {
"blocking multiplexed mode"_test = [] {
const auto tags = makeTestTags(0, 10000);

const std::uint64_t n_samples = static_cast<std::uint64_t>(tags.size() * 10000 + 100000);
gr::Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::testing::TagSource<int32_t>>({ { "n_samples_max", n_samples }, { "mark_tag", false } });
src.tags = tags;
auto &sink = testGraph.emplaceBlock<DataSink<int32_t>>({ { "name", "test_sink" } });
const gr::Size_t n_samples = static_cast<gr::Size_t>(tags.size() * 10000 + 100000);
gr::Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::testing::TagSource<int32_t>>({ { "n_samples_max", n_samples }, { "mark_tag", false } });
src.tags = tags;
auto &sink = testGraph.emplaceBlock<DataSink<int32_t>>({ { "name", "test_sink" } });

expect(eq(ConnectionResult::SUCCESS, testGraph.connect<"out">(src).to<"in">(sink)));

Expand Down Expand Up @@ -552,7 +552,7 @@ const boost::ut::suite DataSinkTests = [] {
};

"blocking polling trigger mode overlapping"_test = [] {
constexpr std::uint64_t kSamples = 150000;
constexpr std::uint32_t kSamples = 150000;
constexpr std::size_t kTriggers = 300;

gr::Graph testGraph;
Expand Down Expand Up @@ -607,7 +607,7 @@ const boost::ut::suite DataSinkTests = [] {
};

"callback trigger mode overlapping"_test = [] {
constexpr std::uint64_t kSamples = 150000;
constexpr std::uint32_t kSamples = 150000;
constexpr std::size_t kTriggers = 300;

gr::Graph testGraph;
Expand Down Expand Up @@ -647,7 +647,7 @@ const boost::ut::suite DataSinkTests = [] {
};

"non-blocking polling continuous mode"_test = [] {
constexpr std::uint64_t kSamples = 200000;
constexpr std::uint32_t kSamples = 200000;

gr::Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::testing::TagSource<float>>({ { "n_samples_max", kSamples }, { "mark_tag", false } });
Expand Down
28 changes: 14 additions & 14 deletions blocks/basic/test/qa_Selector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

template<typename T>
struct repeated_source : public gr::Block<repeated_source<T>> {
std::uint32_t identifier = 0;
std::uint32_t remaining_events_count;
gr::Size_t identifier = 0;
gr::Size_t remaining_events_count;
std::vector<T> values;
std::vector<T>::const_iterator values_next;

Expand Down Expand Up @@ -59,7 +59,7 @@ ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T), (repeated_source<T>), identifi

template<typename T>
struct validator_sink : public gr::Block<validator_sink<T>> {
std::uint32_t identifier = 0;
gr::Size_t identifier = 0;
gr::PortIn<T> in;

std::vector<T> expected_values;
Expand Down Expand Up @@ -114,29 +114,29 @@ struct adder : public gr::Block<adder<T>> {
ENABLE_REFLECTION_FOR_TEMPLATE_FULL((typename T), (adder<T>), addend0, addend1, sum);

struct test_definition {
std::uint32_t value_count;
std::vector<std::pair<std::uint32_t, std::uint32_t>> mapping;
std::vector<std::vector<double>> input_values;
std::vector<std::vector<double>> output_values;
std::uint32_t monitor_source;
std::vector<double> monitor_values;
bool back_pressure;
gr::Size_t value_count;
std::vector<std::pair<gr::Size_t, gr::Size_t>> mapping;
std::vector<std::vector<double>> input_values;
std::vector<std::vector<double>> output_values;
gr::Size_t monitor_source;
std::vector<double> monitor_values;
bool back_pressure;
};

void
execute_selector_test(test_definition definition) {
using namespace boost::ut;

const std::uint32_t sources_count = definition.input_values.size();
const std::uint32_t sinks_count = definition.output_values.size();
const gr::Size_t sources_count = definition.input_values.size();
const gr::Size_t sinks_count = definition.output_values.size();

gr::Graph graph;
std::vector<repeated_source<double> *> sources;
std::vector<validator_sink<double> *> sinks;
gr::basic::Selector<double> *selector;

std::vector<std::uint32_t> mapIn(definition.mapping.size());
std::vector<std::uint32_t> map_out(definition.mapping.size());
std::vector<gr::Size_t> mapIn(definition.mapping.size());
std::vector<gr::Size_t> map_out(definition.mapping.size());
std::ranges::transform(definition.mapping, mapIn.begin(), [](auto &p) { return p.first; });
std::ranges::transform(definition.mapping, map_out.begin(), [](auto &p) { return p.second; });

Expand Down
24 changes: 12 additions & 12 deletions blocks/basic/test/qa_sources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ const boost::ut::suite TagTests = [] {
if (verbose) {
fmt::println("started ClockSource test w/ {}", useIoThreadPool ? "Graph/Block<T> provided-thread" : "user-provided thread");
}
constexpr std::uint32_t n_samples = 1900;
constexpr float sample_rate = 2000.f;
Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::basic::ClockSource<float, useIoThreadPool>>(
constexpr gr::Size_t n_samples = 1900;
constexpr float sample_rate = 2000.f;
Graph testGraph;
auto &src = testGraph.emplaceBlock<gr::basic::ClockSource<float, useIoThreadPool>>(
{ { "sample_rate", sample_rate }, { "n_samples_max", n_samples }, { "name", "ClockSource" }, { "verbose_console", verbose } });
src.tags = {
{ 0, { { "key", "value@0" } } }, //
Expand All @@ -71,8 +71,8 @@ const boost::ut::suite TagTests = [] {

expect(eq(src.n_samples_max, n_samples)) << "src did not accept require max input samples";
expect(eq(src.n_samples_produced, n_samples)) << "src did not produce enough output samples";
expect(eq(static_cast<std::uint32_t>(sink1.n_samples_produced), n_samples)) << fmt::format("sink1 did not consume enough input samples ({} vs. {})", sink1.n_samples_produced, n_samples);
expect(eq(static_cast<std::uint32_t>(sink2.n_samples_produced), n_samples)) << fmt::format("sink2 did not consume enough input samples ({} vs. {})", sink2.n_samples_produced, n_samples);
expect(eq(static_cast<gr::Size_t>(sink1.n_samples_produced), n_samples)) << fmt::format("sink1 did not consume enough input samples ({} vs. {})", sink1.n_samples_produced, n_samples);
expect(eq(static_cast<gr::Size_t>(sink2.n_samples_produced), n_samples)) << fmt::format("sink2 did not consume enough input samples ({} vs. {})", sink2.n_samples_produced, n_samples);

if (std::getenv("DISABLE_SENSITIVE_TESTS") == nullptr) {
expect(approx(sink1.effective_sample_rate(), sample_rate, 500.f))
Expand Down Expand Up @@ -157,12 +157,12 @@ const boost::ut::suite TagTests = [] {
};

"SignalGenerator + ClockSource test"_test = [] {
constexpr std::uint32_t n_samples = 200;
constexpr float sample_rate = 1000.f;
Graph testGraph;
auto &clockSrc = testGraph.emplaceBlock<gr::basic::ClockSource<float>>({ { "sample_rate", sample_rate }, { "n_samples_max", n_samples }, { "name", "ClockSource" } });
auto &signalGen = testGraph.emplaceBlock<SignalGenerator<float>>({ { "sample_rate", sample_rate }, { "name", "SignalGenerator" } });
auto &sink = testGraph.emplaceBlock<TagSink<float, ProcessFunction::USE_PROCESS_ONE>>({ { "name", "TagSink" }, { "verbose_console", true } });
constexpr gr::Size_t n_samples = 200;
constexpr float sample_rate = 1000.f;
Graph testGraph;
auto &clockSrc = testGraph.emplaceBlock<gr::basic::ClockSource<float>>({ { "sample_rate", sample_rate }, { "n_samples_max", n_samples }, { "name", "ClockSource" } });
auto &signalGen = testGraph.emplaceBlock<SignalGenerator<float>>({ { "sample_rate", sample_rate }, { "name", "SignalGenerator" } });
auto &sink = testGraph.emplaceBlock<TagSink<float, ProcessFunction::USE_PROCESS_ONE>>({ { "name", "TagSink" }, { "verbose_console", true } });

expect(eq(ConnectionResult::SUCCESS, testGraph.connect<"out">(clockSrc).to<"in">(signalGen)));
expect(eq(ConnectionResult::SUCCESS, testGraph.connect<"out">(signalGen).to<"in">(sink)));
Expand Down
Loading

0 comments on commit f00b458

Please sign in to comment.