aboutsummaryrefslogtreecommitdiffstats
path: root/host/tests
diff options
context:
space:
mode:
Diffstat (limited to 'host/tests')
-rw-r--r--host/tests/CMakeLists.txt47
-rw-r--r--host/tests/block_id_test.cpp117
-rw-r--r--host/tests/blockdef_test.cpp94
-rw-r--r--host/tests/convert_test.cpp163
-rw-r--r--host/tests/device3_test.cpp175
-rw-r--r--host/tests/devtest/CMakeLists.txt58
-rw-r--r--host/tests/devtest/README.md28
-rwxr-xr-xhost/tests/devtest/benchmark_rate_test.py98
-rwxr-xr-xhost/tests/devtest/devtest_b2xx.py76
-rwxr-xr-xhost/tests/devtest/devtest_e3xx.py58
-rwxr-xr-xhost/tests/devtest/devtest_x3x0.py57
-rwxr-xr-xhost/tests/devtest/gpio_test.py47
-rwxr-xr-xhost/tests/devtest/run_testsuite.py134
-rwxr-xr-xhost/tests/devtest/rx_samples_to_file_test.py67
-rw-r--r--host/tests/devtest/test_messages_test.py57
-rwxr-xr-xhost/tests/devtest/test_pps_test.py51
-rwxr-xr-xhost/tests/devtest/tx_bursts_test.py63
-rwxr-xr-xhost/tests/devtest/uhd_test_base.py222
-rw-r--r--host/tests/devtest/usrp_probe.py50
-rwxr-xr-xhost/tests/devtest/usrp_probe_test.py53
-rw-r--r--host/tests/expert_test.cpp256
-rw-r--r--host/tests/fe_conn_test.cpp108
-rw-r--r--host/tests/graph.hpp51
-rw-r--r--host/tests/graph_search_test.cpp169
-rw-r--r--host/tests/nocscript_common.hpp35
-rw-r--r--host/tests/nocscript_expr_test.cpp451
-rw-r--r--host/tests/nocscript_ftable_test.cpp259
-rw-r--r--host/tests/nocscript_parser_test.cpp167
-rw-r--r--host/tests/node_connect_test.cpp143
-rw-r--r--host/tests/property_test.cpp65
-rw-r--r--host/tests/rate_node_test.cpp139
-rw-r--r--host/tests/stream_sig_test.cpp82
-rw-r--r--host/tests/tick_node_test.cpp110
33 files changed, 3739 insertions, 11 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt
index 8b12c961f..8f7fdcd7c 100644
--- a/host/tests/CMakeLists.txt
+++ b/host/tests/CMakeLists.txt
@@ -45,11 +45,26 @@ SET(test_sources
subdev_spec_test.cpp
time_spec_test.cpp
vrt_test.cpp
+ expert_test.cpp
+ fe_conn_test.cpp
)
#turn each test cpp file into an executable with an int main() function
ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK -DBOOST_TEST_MAIN)
+IF(ENABLE_RFNOC)
+ LIST(APPEND test_sources
+ block_id_test.cpp
+ blockdef_test.cpp
+ device3_test.cpp
+ graph_search_test.cpp
+ node_connect_test.cpp
+ rate_node_test.cpp
+ stream_sig_test.cpp
+ tick_node_test.cpp
+ )
+ENDIF(ENABLE_RFNOC)
+
IF(ENABLE_C_API)
LIST(APPEND test_sources
eeprom_c_test.c
@@ -70,6 +85,36 @@ FOREACH(test_source ${test_sources})
UHD_INSTALL(TARGETS ${test_name} RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests)
ENDFOREACH(test_source)
+# Other tests that don't directly link with libuhd: (TODO find a nicer way to do this)
+INCLUDE_DIRECTORIES(${CMAKE_BINARY_DIR}/lib/rfnoc/nocscript/)
+INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/)
+ADD_EXECUTABLE(nocscript_expr_test
+ nocscript_expr_test.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp
+)
+TARGET_LINK_LIBRARIES(nocscript_expr_test uhd ${Boost_LIBRARIES})
+UHD_ADD_TEST(nocscript_expr_test nocscript_expr_test)
+UHD_INSTALL(TARGETS nocscript_expr_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests)
+
+ADD_EXECUTABLE(nocscript_ftable_test
+ nocscript_ftable_test.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/function_table.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp
+)
+TARGET_LINK_LIBRARIES(nocscript_ftable_test uhd ${Boost_LIBRARIES})
+UHD_ADD_TEST(nocscript_ftable_test nocscript_ftable_test)
+UHD_INSTALL(TARGETS nocscript_ftable_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests)
+
+ADD_EXECUTABLE(nocscript_parser_test
+ nocscript_parser_test.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/parser.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/function_table.cpp
+ ${CMAKE_SOURCE_DIR}/lib/rfnoc/nocscript/expression.cpp
+)
+TARGET_LINK_LIBRARIES(nocscript_parser_test uhd ${Boost_LIBRARIES})
+UHD_ADD_TEST(nocscript_parser_test nocscript_parser_test)
+UHD_INSTALL(TARGETS nocscript_parser_test RUNTIME DESTINATION ${PKG_LIB_DIR}/tests COMPONENT tests)
+
########################################################################
# demo of a loadable module
########################################################################
@@ -77,3 +122,5 @@ IF(MSVC OR APPLE OR LINUX)
ADD_LIBRARY(module_test MODULE module_test.cpp)
TARGET_LINK_LIBRARIES(module_test uhd)
ENDIF()
+
+ADD_SUBDIRECTORY(devtest)
diff --git a/host/tests/block_id_test.cpp b/host/tests/block_id_test.cpp
new file mode 100644
index 000000000..cab8a8cca
--- /dev/null
+++ b/host/tests/block_id_test.cpp
@@ -0,0 +1,117 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <iostream>
+#include <boost/test/unit_test.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/rfnoc/block_id.hpp>
+
+using namespace uhd::rfnoc;
+
+BOOST_AUTO_TEST_CASE(test_block_id) {
+ BOOST_CHECK(block_id_t::is_valid_block_id("00/Filter_1"));
+ BOOST_CHECK(not block_id_t::is_valid_block_id("0/MAG_SQUARE"));
+ BOOST_CHECK(block_id_t::is_valid_blockname("FilterFoo"));
+ BOOST_CHECK(not block_id_t::is_valid_blockname("Filter_Foo"));
+ BOOST_CHECK(not block_id_t::is_valid_blockname("Filter/Foo"));
+ BOOST_CHECK(not block_id_t::is_valid_blockname("0Filter/Foo"));
+ BOOST_CHECK(not block_id_t::is_valid_blockname("0/Filter/Foo"));
+
+ BOOST_REQUIRE_THROW(block_id_t invalid_block_id("0Filter/1"), uhd::value_error);
+
+ block_id_t block_id("0/FFT_1");
+ BOOST_CHECK_EQUAL(block_id.get_device_no(), 0);
+ BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT");
+ BOOST_CHECK_EQUAL(block_id.get_block_count(), 1);
+
+ block_id.set_device_no(17);
+ BOOST_CHECK_EQUAL(block_id.get_device_no(), 17);
+ BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT");
+ BOOST_CHECK_EQUAL(block_id.get_block_count(), 1);
+
+ block_id.set_block_count(11);
+ BOOST_CHECK_EQUAL(block_id.get_device_no(), 17);
+ BOOST_CHECK_EQUAL(block_id.get_block_name(), "FFT");
+ BOOST_CHECK_EQUAL(block_id.get_block_count(), 11);
+
+ block_id.set_block_name("FooBar");
+ BOOST_CHECK_EQUAL(block_id.get_device_no(), 17);
+ BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar");
+ BOOST_CHECK_EQUAL(block_id.get_block_count(), 11);
+
+ BOOST_CHECK(not block_id.set_block_name("Foo_Bar"));
+ BOOST_CHECK_EQUAL(block_id.get_device_no(), 17);
+ BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar"); // Is unchanged because invalid
+ BOOST_CHECK_EQUAL(block_id.get_block_count(), 11);
+
+ block_id++;
+ BOOST_CHECK_EQUAL(block_id.get_device_no(), 17);
+ BOOST_CHECK_EQUAL(block_id.get_block_name(), "FooBar");
+ BOOST_CHECK_EQUAL(block_id.get_block_count(), 12);
+
+ block_id_t other_block_id(7, "BlockName", 3);
+ BOOST_CHECK_EQUAL(other_block_id.get_device_no(), 7);
+ BOOST_CHECK_EQUAL(other_block_id.get_block_name(), "BlockName");
+ BOOST_CHECK_EQUAL(other_block_id.get_block_count(), 3);
+ BOOST_CHECK_EQUAL(other_block_id.to_string(), "7/BlockName_3");
+
+ // Cast
+ std::string block_id_str = std::string(other_block_id);
+ std::cout << "Should print '7/BlockName_3': " << block_id_str << std::endl;
+ BOOST_CHECK_EQUAL(block_id_str, "7/BlockName_3");
+
+ // Operators
+ std::cout << "Testing ostream printing (<<): " << other_block_id << std::endl;
+ BOOST_CHECK_EQUAL(other_block_id, block_id_str);
+ BOOST_CHECK_EQUAL(other_block_id, "7/BlockName_3");
+
+ // match()
+ BOOST_CHECK(other_block_id.match("BlockName"));
+ BOOST_CHECK(other_block_id.match("7/BlockName"));
+ BOOST_CHECK(other_block_id.match("BlockName_3"));
+ BOOST_CHECK(other_block_id.match("7/BlockName_3"));
+ BOOST_CHECK(not other_block_id.match("8/BlockName"));
+ BOOST_CHECK(not other_block_id.match("8/BlockName_3"));
+ BOOST_CHECK(not other_block_id.match("Block_Name_3"));
+ BOOST_CHECK(not other_block_id.match("BlockName_4"));
+ BOOST_CHECK(not other_block_id.match("BlockName_X"));
+ BOOST_CHECK(not other_block_id.match("2093ksdjfflsdkjf"));
+}
+
+BOOST_AUTO_TEST_CASE(test_block_id_set) {
+ // test set()
+ block_id_t block_id_for_set(5, "Blockname", 9);
+ block_id_for_set.set("FirFilter");
+ BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 5);
+ BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "FirFilter");
+ BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 9);
+ block_id_for_set.set("1/FirFilter2");
+ BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 1);
+ BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "FirFilter2");
+ BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 9);
+ block_id_for_set.set("Sync_3");
+ BOOST_CHECK_EQUAL(block_id_for_set.get_device_no(), 1);
+ BOOST_CHECK_EQUAL(block_id_for_set.get_block_name(), "Sync");
+ BOOST_CHECK_EQUAL(block_id_for_set.get_block_count(), 3);
+}
+
+BOOST_AUTO_TEST_CASE(test_block_id_cmp) {
+ BOOST_CHECK(block_id_t("0/FFT_1") == block_id_t("0/FFT_1"));
+ BOOST_CHECK(block_id_t("0/FFT_1") != block_id_t("1/FFT_1"));
+ BOOST_CHECK(block_id_t("0/FFT_1") < block_id_t("1/aaaaaaaaa_0"));
+ BOOST_CHECK(not (block_id_t("0/FFT_1") > block_id_t("1/aaaaaaaaa_0")));
+}
diff --git a/host/tests/blockdef_test.cpp b/host/tests/blockdef_test.cpp
new file mode 100644
index 000000000..2cd06a43a
--- /dev/null
+++ b/host/tests/blockdef_test.cpp
@@ -0,0 +1,94 @@
+//
+// Copyright 2014-2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <iostream>
+#include <map>
+#include <boost/cstdint.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/test/unit_test.hpp>
+#include <boost/format.hpp>
+#include <boost/foreach.hpp>
+#include <uhd/rfnoc/blockdef.hpp>
+
+using namespace uhd::rfnoc;
+
+BOOST_AUTO_TEST_CASE(test_lookup) {
+ std::map<boost::uint64_t, std::string> blocknames = boost::assign::list_of< std::pair<boost::uint64_t, std::string> >
+ (0, "NullSrcSink")
+ (0xFF70000000000000, "FFT")
+ (0xF112000000000001, "FIR")
+ (0xF1F0000000000000, "FIFO")
+ (0xD053000000000000, "Window")
+ (0x5CC0000000000000, "SchmidlCox")
+ ;
+
+ std::cout << blocknames.size() << std::endl;
+
+ for (std::map<boost::uint64_t, std::string>::iterator it = blocknames.begin(); it != blocknames.end(); ++it) {
+ std::cout << "Testing " << it->second << " => " << str(boost::format("%016X") % it->first) << std::endl;
+ blockdef::sptr block_definition = blockdef::make_from_noc_id(it->first);
+ // If the previous function fails, it'll return a NULL pointer
+ BOOST_REQUIRE(block_definition);
+ BOOST_CHECK(block_definition->is_block());
+ BOOST_CHECK_EQUAL(block_definition->get_name(), it->second);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_ports) {
+ // Create an FFT:
+ blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000);
+ blockdef::ports_t in_ports = block_definition->get_input_ports();
+ BOOST_REQUIRE_EQUAL(in_ports.size(), 1);
+ BOOST_CHECK_EQUAL(in_ports[0]["name"], "in");
+ BOOST_CHECK_EQUAL(in_ports[0]["type"], "sc16");
+ BOOST_CHECK(in_ports[0].has_key("vlen"));
+ BOOST_CHECK(in_ports[0].has_key("pkt_size"));
+
+ blockdef::ports_t out_ports = block_definition->get_output_ports();
+ BOOST_REQUIRE_EQUAL(out_ports.size(), 1);
+ BOOST_CHECK_EQUAL(out_ports[0]["name"], "out");
+ BOOST_CHECK(out_ports[0].has_key("vlen"));
+ BOOST_CHECK(out_ports[0].has_key("pkt_size"));
+
+ BOOST_CHECK_EQUAL(block_definition->get_all_port_numbers().size(), 1);
+ BOOST_CHECK_EQUAL(block_definition->get_all_port_numbers()[0], 0);
+}
+
+BOOST_AUTO_TEST_CASE(test_args) {
+ // Create an FFT:
+ blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000);
+ blockdef::args_t args = block_definition->get_args();
+ BOOST_REQUIRE(args.size() >= 3);
+ BOOST_CHECK_EQUAL(args[0]["name"], "spp");
+ BOOST_CHECK_EQUAL(args[0]["type"], "int");
+ BOOST_CHECK_EQUAL(args[0]["value"], "256");
+}
+
+BOOST_AUTO_TEST_CASE(test_regs) {
+ // Create an FFT:
+ blockdef::sptr block_definition = blockdef::make_from_noc_id(0xFF70000000000000);
+ blockdef::registers_t sregs = block_definition->get_settings_registers();
+ BOOST_REQUIRE_EQUAL(sregs.size(), 3);
+ BOOST_CHECK_EQUAL(sregs["FFT_RESET"], 131);
+ BOOST_CHECK_EQUAL(sregs["FFT_SIZE_LOG2"], 132);
+ BOOST_CHECK_EQUAL(sregs["MAGNITUDE_OUT"], 133);
+ blockdef::registers_t user_regs = block_definition->get_readback_registers();
+ BOOST_REQUIRE_EQUAL(user_regs.size(), 2);
+ BOOST_CHECK_EQUAL(user_regs["RB_FFT_RESET"], 0);
+ BOOST_CHECK_EQUAL(user_regs["RB_MAGNITUDE_OUT"], 1);
+}
+
diff --git a/host/tests/convert_test.cpp b/host/tests/convert_test.cpp
index d71d756dd..8d359d2e2 100644
--- a/host/tests/convert_test.cpp
+++ b/host/tests/convert_test.cpp
@@ -417,16 +417,16 @@ BOOST_AUTO_TEST_CASE(test_convert_types_sc16_and_sc8){
}
/***********************************************************************
- * Test short conversion
+ * Test u8 conversion
**********************************************************************/
static void test_convert_types_u8(
size_t nsamps, convert::id_type &id
){
//fill the input samples
std::vector<boost::uint8_t> input(nsamps), output(nsamps);
- //BOOST_FOREACH(boost::uint8_t &in, input) in = boost::uint8_t(std::rand() & 0xFF);
- boost::uint32_t d = 48;
- BOOST_FOREACH(boost::uint8_t &in, input) in = d++;
+ BOOST_FOREACH(boost::uint8_t &in, input) in = boost::uint8_t(std::rand() & 0xFF);
+ //boost::uint32_t d = 48;
+ //BOOST_FOREACH(boost::uint8_t &in, input) in = d++;
//run the loopback and test
convert::id_type in_id = id;
@@ -455,3 +455,158 @@ BOOST_AUTO_TEST_CASE(test_convert_types_u8_and_u8){
test_convert_types_u8(nsamps, id);
}
}
+
+/***********************************************************************
+ * Test s8 conversion
+ **********************************************************************/
+static void test_convert_types_s8(
+ size_t nsamps, convert::id_type &id
+){
+ //fill the input samples
+ std::vector<boost::int8_t> input(nsamps), output(nsamps);
+ BOOST_FOREACH(boost::int8_t &in, input) in = boost::int8_t(std::rand() & 0xFF);
+
+ //run the loopback and test
+ convert::id_type in_id = id;
+ convert::id_type out_id = id;
+ std::swap(out_id.input_format, out_id.output_format);
+ std::swap(out_id.num_inputs, out_id.num_outputs);
+ loopback(nsamps, in_id, out_id, input, output);
+ BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
+}
+
+BOOST_AUTO_TEST_CASE(test_convert_types_s8_and_s8){
+ convert::id_type id;
+ id.input_format = "s8";
+ id.num_inputs = 1;
+ id.num_outputs = 1;
+
+ //try various lengths to test edge cases
+ id.output_format = "s8_item32_le";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_s8(nsamps, id);
+ }
+
+ //try various lengths to test edge cases
+ id.output_format = "s8_item32_be";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_s8(nsamps, id);
+ }
+}
+
+/***********************************************************************
+ * Test s16 conversion
+ **********************************************************************/
+static void test_convert_types_s16(
+ size_t nsamps, convert::id_type &id
+){
+ //fill the input samples
+ std::vector<boost::int16_t> input(nsamps), output(nsamps);
+ BOOST_FOREACH(boost::int16_t &in, input) in = boost::int16_t(std::rand() & 0xFFFF);
+
+ //run the loopback and test
+ convert::id_type in_id = id;
+ convert::id_type out_id = id;
+ std::swap(out_id.input_format, out_id.output_format);
+ std::swap(out_id.num_inputs, out_id.num_outputs);
+ loopback(nsamps, in_id, out_id, input, output);
+ BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
+}
+
+BOOST_AUTO_TEST_CASE(test_convert_types_s16_and_s16){
+ convert::id_type id;
+ id.input_format = "s16";
+ id.num_inputs = 1;
+ id.num_outputs = 1;
+
+ //try various lengths to test edge cases
+ id.output_format = "s16_item32_le";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_s16(nsamps, id);
+ }
+
+ //try various lengths to test edge cases
+ id.output_format = "s16_item32_be";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_s16(nsamps, id);
+ }
+}
+
+/***********************************************************************
+ * Test fc32 -> fc32 conversion
+ **********************************************************************/
+static void test_convert_types_fc32(
+ size_t nsamps, convert::id_type &id
+){
+ //fill the input samples
+ std::vector< std::complex<float> > input(nsamps), output(nsamps);
+ BOOST_FOREACH(fc32_t &in, input) in = fc32_t(
+ (std::rand()/float(RAND_MAX/2)) - 1,
+ (std::rand()/float(RAND_MAX/2)) - 1
+ );
+
+ //run the loopback and test
+ convert::id_type in_id = id;
+ convert::id_type out_id = id;
+ std::swap(out_id.input_format, out_id.output_format);
+ std::swap(out_id.num_inputs, out_id.num_outputs);
+ loopback(nsamps, in_id, out_id, input, output);
+ BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
+}
+
+BOOST_AUTO_TEST_CASE(test_convert_types_fc32_and_fc32){
+ convert::id_type id;
+ id.input_format = "fc32";
+ id.num_inputs = 1;
+ id.num_outputs = 1;
+
+ //try various lengths to test edge cases
+ id.output_format = "fc32_item32_le";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_fc32(nsamps, id);
+ }
+
+ //try various lengths to test edge cases
+ id.output_format = "fc32_item32_be";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_fc32(nsamps, id);
+ }
+}
+
+/***********************************************************************
+ * Test f32 -> f32 conversion
+ **********************************************************************/
+static void test_convert_types_f32(
+ size_t nsamps, convert::id_type &id
+){
+ //fill the input samples
+ std::vector<float> input(nsamps), output(nsamps);
+ BOOST_FOREACH(float &in, input) in = float((std::rand()/float(RAND_MAX/2)) - 1);
+
+ //run the loopback and test
+ convert::id_type in_id = id;
+ convert::id_type out_id = id;
+ std::swap(out_id.input_format, out_id.output_format);
+ std::swap(out_id.num_inputs, out_id.num_outputs);
+ loopback(nsamps, in_id, out_id, input, output);
+ BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
+}
+
+BOOST_AUTO_TEST_CASE(test_convert_types_f32_and_f32){
+ convert::id_type id;
+ id.input_format = "f32";
+ id.num_inputs = 1;
+ id.num_outputs = 1;
+
+ //try various lengths to test edge cases
+ id.output_format = "f32_item32_le";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_f32(nsamps, id);
+ }
+
+ //try various lengths to test edge cases
+ id.output_format = "f32_item32_be";
+ for (size_t nsamps = 1; nsamps < 16; nsamps++){
+ test_convert_types_f32(nsamps, id);
+ }
+}
diff --git a/host/tests/device3_test.cpp b/host/tests/device3_test.cpp
new file mode 100644
index 000000000..593facb9a
--- /dev/null
+++ b/host/tests/device3_test.cpp
@@ -0,0 +1,175 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <exception>
+#include <iostream>
+#include <boost/test/unit_test.hpp>
+#include <uhd/property_tree.hpp>
+#include <uhd/types/wb_iface.hpp>
+#include <uhd/device3.hpp>
+#include <uhd/rfnoc/block_ctrl.hpp>
+#include <uhd/rfnoc/graph.hpp>
+
+using namespace uhd;
+using namespace uhd::rfnoc;
+
+static const boost::uint64_t TEST_NOC_ID = 0xAAAABBBBCCCCDDDD;
+static const sid_t TEST_SID0 = 0x00000200; // 0.0.2.0
+static const sid_t TEST_SID1 = 0x00000210; // 0.0.2.F
+
+// Pseudo-wb-iface
+class pseudo_wb_iface_impl : public uhd::wb_iface
+{
+ public:
+ pseudo_wb_iface_impl() {};
+ ~pseudo_wb_iface_impl() {};
+
+ void poke64(const wb_addr_type addr, const boost::uint64_t data) {
+ std::cout << str(boost::format("[PSEUDO] poke64 to addr: %016X, data == %016X") % addr % data) << std::endl;
+ };
+
+ boost::uint64_t peek64(const wb_addr_type addr) {
+ std::cout << str(boost::format("[PSEUDO] peek64 to addr: %016X") % addr) << std::endl;
+ switch (addr) {
+ case SR_READBACK_REG_ID:
+ return TEST_NOC_ID;
+ case SR_READBACK_REG_FIFOSIZE:
+ return 0x000000000000000B;
+ case SR_READBACK_REG_USER:
+ return 0x0123456789ABCDEF;
+ default:
+ return 0;
+ }
+ return 0;
+ }
+
+ void poke32(const wb_addr_type addr, const boost::uint32_t data) {
+ std::cout << str(boost::format("poke32 to addr: %08X, data == %08X") % addr % data) << std::endl;
+ }
+
+ boost::uint32_t peek32(const wb_addr_type addr) {
+ std::cout << str(boost::format("peek32 to addr: %08X") % addr) << std::endl;
+ return 0;
+ }
+};
+
+// Pseudo-device
+class pseudo_device3_impl : public uhd::device3
+{
+ public:
+ pseudo_device3_impl()
+ {
+ _tree = uhd::property_tree::make();
+ _tree->create<std::string>("/name").set("Test Pseudo-Device3");
+
+ // We can re-use this:
+ std::map<size_t, wb_iface::sptr> ctrl_ifaces = boost::assign::map_list_of
+ (0, wb_iface::sptr(new pseudo_wb_iface_impl()))
+ ;
+
+ // Add two block controls:
+ uhd::rfnoc::make_args_t make_args;
+ make_args.ctrl_ifaces = ctrl_ifaces;
+ make_args.base_address = TEST_SID0.get_dst();
+ make_args.device_index = 0;
+ make_args.tree = _tree;
+ make_args.is_big_endian = false;
+ std::cout << "[PSEUDO] Generating block controls 1/2:" << std::endl;
+ _rfnoc_block_ctrl.push_back( block_ctrl_base::make(make_args) );
+
+ std::cout << "[PSEUDO] Generating block controls 2/2:" << std::endl;
+ make_args.base_address = TEST_SID1.get_dst();
+ _rfnoc_block_ctrl.push_back( block_ctrl::make(make_args) );
+ }
+
+ rx_streamer::sptr get_rx_stream(const stream_args_t &args) {
+ throw uhd::not_implemented_error(args.args.to_string());
+ }
+
+ tx_streamer::sptr get_tx_stream(const stream_args_t &args) {
+ throw uhd::not_implemented_error(args.args.to_string());
+ }
+
+ bool recv_async_msg(async_metadata_t &async_metadata, double timeout) {
+ throw uhd::not_implemented_error(str(boost::format("%d %f") % async_metadata.channel % timeout));
+ }
+
+ rfnoc::graph::sptr create_graph(const std::string &) { return rfnoc::graph::sptr(); }
+};
+
+device3::sptr make_pseudo_device()
+{
+ return device3::sptr(new pseudo_device3_impl());
+}
+
+class dummy_block_ctrl : public block_ctrl {
+ int foo;
+};
+
+BOOST_AUTO_TEST_CASE(test_device3) {
+ device3::sptr my_device = make_pseudo_device();
+
+ std::cout << "Checking block 0..." << std::endl;
+ BOOST_REQUIRE(my_device->find_blocks("Block").size());
+
+ std::cout << "Getting block 0..." << std::endl;
+ block_ctrl_base::sptr block0 = my_device->get_block_ctrl(my_device->find_blocks("Block")[0]);
+ BOOST_REQUIRE(block0);
+ BOOST_CHECK_EQUAL(block0->get_block_id(), "0/Block_0");
+
+ std::cout << "Checking block 1..." << std::endl;
+ BOOST_REQUIRE(my_device->has_block(block_id_t("0/Block_1")));
+
+ std::cout << "Getting block 1..." << std::endl;
+ block_ctrl_base::sptr block1 = my_device->get_block_ctrl(block_id_t("0/Block_1"));
+ BOOST_REQUIRE(block1);
+ BOOST_CHECK_EQUAL(block1->get_block_id(), "0/Block_1");
+}
+
+BOOST_AUTO_TEST_CASE(test_device3_cast) {
+ device3::sptr my_device = make_pseudo_device();
+
+ std::cout << "Getting block 0..." << std::endl;
+ block_ctrl::sptr block0 = my_device->get_block_ctrl<block_ctrl>(block_id_t("0/Block_0"));
+ BOOST_REQUIRE(block0);
+ BOOST_CHECK_EQUAL(block0->get_block_id(), "0/Block_0");
+
+ std::cout << "Getting block 1..." << std::endl;
+ block_ctrl_base::sptr block1 = my_device->get_block_ctrl<block_ctrl>(block_id_t("0/Block_1"));
+ BOOST_CHECK_EQUAL(block1->get_block_id(), "0/Block_1");
+}
+
+BOOST_AUTO_TEST_CASE(test_device3_fail) {
+ device3::sptr my_device = make_pseudo_device();
+
+ BOOST_CHECK(not my_device->has_block(block_id_t("0/FooBarBlock_0")));
+ BOOST_CHECK(not my_device->has_block<dummy_block_ctrl>(block_id_t("0/Block_1")));
+
+ BOOST_CHECK(my_device->find_blocks("FooBarBlock").size() == 0);
+ BOOST_CHECK(my_device->find_blocks<block_ctrl>("FooBarBlock").size() == 0);
+
+ BOOST_REQUIRE_THROW(
+ my_device->get_block_ctrl(block_id_t("0/FooBarBlock_17")),
+ uhd::lookup_error
+ );
+ BOOST_REQUIRE_THROW(
+ my_device->get_block_ctrl<dummy_block_ctrl>(block_id_t("0/Block_1")),
+ uhd::lookup_error
+ );
+}
+
+// vim: sw=4 et:
diff --git a/host/tests/devtest/CMakeLists.txt b/host/tests/devtest/CMakeLists.txt
new file mode 100644
index 000000000..6fa921bbd
--- /dev/null
+++ b/host/tests/devtest/CMakeLists.txt
@@ -0,0 +1,58 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# Formatting
+MESSAGE(STATUS "")
+
+# All devtest files get installed:
+FILE(GLOB py_devtest_files "*.py")
+UHD_INSTALL(PROGRAMS
+ ${py_devtest_files}
+ DESTINATION ${PKG_LIB_DIR}/tests/devtest
+ COMPONENT tests
+)
+
+# Arguments:
+# - pattern: This will be used to identify which devtest_*.py is to be executed.
+# - filter: Will be used in args strings as "type=<filter>".
+# - devtype: A descriptive string. Is only used for CMake output.
+MACRO(ADD_DEVTEST pattern filter devtype)
+ MESSAGE(STATUS "Adding ${devtype} device test target")
+ ADD_CUSTOM_TARGET("test_${pattern}"
+ ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/run_testsuite.py
+ "--src-dir" "${CMAKE_CURRENT_SOURCE_DIR}"
+ "--devtest-pattern" "${pattern}"
+ "--device-filter" "${filter}"
+ "--build-type" "${CMAKE_BUILD_TYPE}"
+ "--build-dir" "${CMAKE_BINARY_DIR}"
+ COMMENT "Running device test on all connected ${devtype} devices:"
+ WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
+ )
+ENDMACRO(ADD_DEVTEST)
+
+IF(ENABLE_B200)
+ ADD_DEVTEST("b2xx" "b200" "B2XX")
+ENDIF(ENABLE_B200)
+IF(ENABLE_X300)
+ ADD_DEVTEST("x3x0" "x300" "X3x0")
+ENDIF(ENABLE_X300)
+IF(ENABLE_E300)
+ ADD_DEVTEST("e3xx" "e3x0" "E3XX")
+ENDIF(ENABLE_E300)
+
+# Formatting
+MESSAGE(STATUS "")
diff --git a/host/tests/devtest/README.md b/host/tests/devtest/README.md
new file mode 100644
index 000000000..ee1ff3c9f
--- /dev/null
+++ b/host/tests/devtest/README.md
@@ -0,0 +1,28 @@
+# Device Tests
+
+These are a set of tests to be run with one or more attached devices.
+None of these tests require special configuration; e.g., the X3x0 test
+will work regardless of attached daughterboards, FPGIO wiring etc.
+
+## Adding new tests
+
+To add new tests, add new files with classes that derive from unittest.TestCase.
+Most of the time, you'll want to derive from `uhd_test_case` or
+`uhd_example_test_case`.
+
+## Adding new devices
+
+To add new devices, follow these steps:
+
+1) Add an entry to the CMakeLists.txt file in this directory using the
+ `ADD_DEVTEST()` macro.
+2) Add a `devtest_pattern.py` file to this directory, where `pattern` is
+ the same pattern used in the `ADD_DEVTEST()` macro.
+3) Edit this devtest file to import all the tests you want to run. Some
+ may require parameterization.
+
+The devtest file is 'executed' using Python's unittest module, so it doesn't
+require any actual commands. If the device needs special initialization,
+commands inside this file will be executed *if* they are *not* in a
+`if __name__ == "__main__"` conditional.
+
diff --git a/host/tests/devtest/benchmark_rate_test.py b/host/tests/devtest/benchmark_rate_test.py
new file mode 100755
index 000000000..6c5a75d7f
--- /dev/null
+++ b/host/tests/devtest/benchmark_rate_test.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2016 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Test using benchmark_rate. """
+
+import re
+from uhd_test_base import uhd_example_test_case
+
+class uhd_benchmark_rate_test(uhd_example_test_case):
+ """
+ Run benchmark_rate in various configurations.
+ """
+ tests = {}
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = uhd_benchmark_rate_test.tests
+
+ def run_test(self, test_name, test_args):
+ """
+ Runs benchmark_rate with the given parameters. Parses output and writes
+ results to file.
+
+ We always run both tx and rx.
+ """
+ rel_samp_err_threshold = 0.1 # 10% off is still quite generous
+ samp_rate = test_args.get('rate', 1e6)
+ duration = test_args.get('duration', 1)
+ chan = test_args.get('chan', '0')
+ n_chans = len(chan.split(","))
+ expected_samples = n_chans * duration * samp_rate
+ self.log.info('Running test {n}, Channel = {c}, Sample Rate = {r}'.format(
+ n=test_name, c=chan, r=samp_rate,
+ ))
+ args = [
+ self.create_addr_args_str(),
+ '--duration', str(duration),
+ '--channels', str(chan),
+ ]
+ if 'tx' in test_args.get('direction', ''):
+ args.append('--tx_rate')
+ args.append(str(samp_rate))
+ if 'rx' in test_args.get('direction', ''):
+ args.append('--rx_rate')
+ args.append(str(samp_rate))
+ (app, run_results) = self.run_example('benchmark_rate', args)
+ match = re.search(r'(Num received samples):\s*(.*)', app.stdout)
+ run_results['num_rx_samples'] = int(match.group(2)) if match else -1
+ if run_results['num_rx_samples'] != -1:
+ run_results['rel_rx_samples_error'] = 1.0 * abs(run_results['num_rx_samples'] - expected_samples) / expected_samples
+ else:
+ run_results['rel_rx_samples_error'] = 100
+ match = re.search(r'(Num dropped samples):\s*(.*)', app.stdout)
+ run_results['num_rx_dropped'] = int(match.group(2)) if match else -1
+ match = re.search(r'(Num overflows detected):\s*(.*)', app.stdout)
+ run_results['num_rx_overruns'] = int(match.group(2)) if match else -1
+ match = re.search(r'(Num transmitted samples):\s*(.*)', app.stdout)
+ run_results['num_tx_samples'] = int(match.group(2)) if match else -1
+ if run_results['num_tx_samples'] != -1:
+ run_results['rel_tx_samples_error'] = 1.0 * abs(run_results['num_tx_samples'] - expected_samples) / expected_samples
+ else:
+ run_results['rel_tx_samples_error'] = 100
+ match = re.search(r'(Num sequence errors):\s*(.*)', app.stdout)
+ run_results['num_tx_seqerrs'] = int(match.group(2)) if match else -1
+ match = re.search(r'(Num underflows detected):\s*(.*)', app.stdout)
+ run_results['num_tx_underruns'] = int(match.group(2)) if match else -1
+ match = re.search(r'(Num timeouts):\s*(.*)', app.stdout)
+ run_results['num_timeouts'] = int(match.group(2)) if match else -1
+ run_results['passed'] = all([
+ run_results['return_code'] == 0,
+ run_results['num_rx_dropped'] == 0,
+ run_results['num_tx_seqerrs'] == 0,
+ run_results['num_tx_underruns'] <= test_args.get('acceptable-underruns', 0),
+ run_results['num_rx_samples'] > 0,
+ run_results['num_tx_samples'] > 0,
+ run_results['num_timeouts'] == 0,
+ run_results['rel_rx_samples_error'] < rel_samp_err_threshold,
+ run_results['rel_tx_samples_error'] < rel_samp_err_threshold,
+ ])
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/devtest/devtest_b2xx.py b/host/tests/devtest/devtest_b2xx.py
new file mode 100755
index 000000000..4b81f0afe
--- /dev/null
+++ b/host/tests/devtest/devtest_b2xx.py
@@ -0,0 +1,76 @@
+#
+# Copyright 2015-2016 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Run device tests for the B2xx series.
+"""
+from usrp_probe_test import uhd_usrp_probe_test
+from benchmark_rate_test import uhd_benchmark_rate_test
+uhd_benchmark_rate_test.tests = {
+ 'mimo': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'channels': ['0,1',],
+ 'sample-rates': [1e6],
+ 'products': ['B210',],
+ 'acceptable-underruns': 500,
+ },
+ 'siso_chan0_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0',
+ 'rate': 1e6,
+ 'acceptable-underruns': 50,
+ },
+ #'siso_chan0_fast': {
+ #'duration': 1,
+ #'direction': 'tx,rx',
+ #'chan': '0',
+ #'rate': 40e6,
+ #'acceptable-underruns': 500,
+ #},
+ 'siso_chan1_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 50,
+ 'products': ['B210',],
+ },
+ #'siso_chan1_fast': {
+ #'duration': 1,
+ #'direction': 'tx,rx',
+ #'chan': '1',
+ #'rate': 40e6,
+ #'acceptable-underruns': 500,
+ #'products': ['B210',],
+ #},
+}
+
+from rx_samples_to_file_test import rx_samples_to_file_test
+rx_samples_to_file_test.tests = {
+ 'default': {
+ 'duration': 1,
+ 'subdev': 'A:A',
+ 'rate': 5e6,
+ 'products': ['B210', 'B200',],
+ },
+}
+
+from tx_bursts_test import uhd_tx_bursts_test
+from test_pps_test import uhd_test_pps_test
+from gpio_test import gpio_test
+
diff --git a/host/tests/devtest/devtest_e3xx.py b/host/tests/devtest/devtest_e3xx.py
new file mode 100755
index 000000000..1cab44184
--- /dev/null
+++ b/host/tests/devtest/devtest_e3xx.py
@@ -0,0 +1,58 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Run device tests for the E3XX series.
+"""
+from usrp_probe_test import uhd_usrp_probe_test
+from benchmark_rate_test import uhd_benchmark_rate_test
+uhd_benchmark_rate_test.tests = {
+ 'mimo': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'channels': '0,1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 500,
+ },
+ 'siso_chan0_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0',
+ 'rate': 1e6,
+ 'acceptable-underruns': 50,
+ },
+ 'siso_chan1_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 50,
+ 'products': ['B210',],
+ },
+}
+
+from rx_samples_to_file_test import rx_samples_to_file_test
+rx_samples_to_file_test.tests = {
+ 'default': {
+ 'duration': 1,
+ 'subdev': 'A:A',
+ 'rate': 5e6,
+ },
+}
+
+from tx_bursts_test import uhd_tx_bursts_test
+from test_pps_test import uhd_test_pps_test
+
diff --git a/host/tests/devtest/devtest_x3x0.py b/host/tests/devtest/devtest_x3x0.py
new file mode 100755
index 000000000..7ad6b21b6
--- /dev/null
+++ b/host/tests/devtest/devtest_x3x0.py
@@ -0,0 +1,57 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Run device tests for the X3x0 series.
+"""
+
+from benchmark_rate_test import uhd_benchmark_rate_test
+uhd_benchmark_rate_test.tests = {
+ 'mimo_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0,1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 500,
+ },
+ 'mimo_fast': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0,1',
+ 'rate': 12.5e6,
+ 'acceptable-underruns': 500,
+ },
+ 'siso_chan0_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '0',
+ 'rate': 1e6,
+ 'acceptable-underruns': 0,
+ },
+ 'siso_chan1_slow': {
+ 'duration': 1,
+ 'direction': 'tx,rx',
+ 'chan': '1',
+ 'rate': 1e6,
+ 'acceptable-underruns': 0,
+ },
+}
+
+#from rx_samples_to_file_test import rx_samples_to_file_test
+from tx_bursts_test import uhd_tx_bursts_test
+from test_pps_test import uhd_test_pps_test
+from gpio_test import gpio_test
+
diff --git a/host/tests/devtest/gpio_test.py b/host/tests/devtest/gpio_test.py
new file mode 100755
index 000000000..d764a8d96
--- /dev/null
+++ b/host/tests/devtest/gpio_test.py
@@ -0,0 +1,47 @@
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Test for test_pps_input. """
+
+import re
+from uhd_test_base import uhd_example_test_case
+
+class gpio_test(uhd_example_test_case):
+ """ Run gpio. """
+ tests = {'default': {},}
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = gpio_test.tests
+
+ def run_test(self, test_name, test_args):
+ """ Run the app and scrape for the success message. """
+ self.log.info('Running test {n}'.format(n=test_name,))
+ # Run example:
+ args = [
+ self.create_addr_args_str(),
+ ]
+ (app, run_results) = self.run_example('gpio', args)
+ # Evaluate pass/fail:
+ run_results['passed'] = all([
+ app.returncode == 0,
+ re.search('All tests passed!', app.stdout) is not None,
+ ])
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/devtest/run_testsuite.py b/host/tests/devtest/run_testsuite.py
new file mode 100755
index 000000000..2826f25e9
--- /dev/null
+++ b/host/tests/devtest/run_testsuite.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2016 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+Device test runner.
+"""
+
+from __future__ import print_function
+import os
+import sys
+import subprocess
+import argparse
+import logging
+from usrp_probe import get_usrp_list
+
+def setup_parser():
+ """ Set up argparser """
+ parser = argparse.ArgumentParser(description="Test utility for UHD/USRP.")
+ parser.add_argument('--devtest-pattern', '-p', default='*', help='e.g. b2xx')
+ parser.add_argument('--device-filter', '-f', default=None, required=True, help='b200, x300, ...')
+ parser.add_argument('--log-dir', '-l', default='.')
+ parser.add_argument('--src-dir', default='.', help='Directory where the test sources are stored')
+ parser.add_argument('--build-dir', default=None, help='Build dir (where examples/ and utils/ are)')
+ parser.add_argument('--build-type', default='Release')
+ return parser
+
+def setup_env(args):
+ " Add build dir into lib + exe paths, depending on OS "
+ def setup_env_win(env, build_dir, build_type):
+ " Add build dir into paths (Windows)"
+ env['PATH'] = "{build_dir}/lib/{build_type};" + \
+ "{build_dir}/examples/{build_type};" + \
+ "{build_dir}/utils/{build_type};{path}".format(
+ build_dir=build_dir, build_type=build_type, path=env.get('PATH', '')
+ )
+ env['LIBPATH'] = "{build_dir}/lib/{build_type};{path}".format(
+ build_dir=build_dir, build_type=build_type, path=env.get('LIBPATH', '')
+ )
+ env['LIB'] = "{build_dir}/lib/{build_type};{path}".format(
+ build_dir=build_dir, build_type=build_type, path=env.get('LIB', '')
+ )
+ return env
+ def setup_env_unix(env, build_dir):
+ " Add build dir into paths (Unices)"
+ env['PATH'] = "{build_dir}/examples:{build_dir}/utils:{path}".format(
+ build_dir=build_dir, path=env.get('PATH', '')
+ )
+ env['LD_LIBRARY_PATH'] = "{build_dir}/lib:{path}".format(
+ build_dir=build_dir, path=env.get('LD_LIBRARY_PATH', '')
+ )
+ return env
+ def setup_env_osx(env, build_dir):
+ " Add build dir into paths (OS X)"
+ env['PATH'] = "{build_dir}/examples:{build_dir}/utils:{path}".format(
+ build_dir=build_dir, path=env.get('PATH', '')
+ )
+ env['DYLD_LIBRARY_PATH'] = "{build_dir}/lib:{path}".format(
+ build_dir=build_dir, path=env.get('DYLD_LIBRARY_PATH', '')
+ )
+ return env
+ ### Go
+ env = os.environ
+ if sys.platform.startswith('linux'):
+ env = setup_env_unix(env, args.build_dir)
+ elif sys.platform.startswith('win'):
+ env = setup_env_win(env, args.build_dir, args.build_type)
+ elif sys.platform.startswith('darwin'):
+ env = setup_env_osx(env, args.build_dir)
+ else:
+ print("Devtest not supported on this platform ({0}).".format(sys.platform))
+ exit(1)
+ return env
+
+def main():
+ " Go, go, go! "
+ args = setup_parser().parse_args()
+ env = setup_env(args)
+ devtest_pattern = "devtest_{p}.py".format(p=args.devtest_pattern)
+ uhd_args_list = get_usrp_list("type=" + args.device_filter, env)
+ if len(uhd_args_list) == 0:
+ print("No devices found. Exiting.")
+ exit(1)
+ tests_passed = True
+ for uhd_idx, uhd_info in enumerate(uhd_args_list):
+ print('--- Running all tests for device {dev} ({prod}, Serial: {ser}).'.format(
+ dev=uhd_idx,
+ prod=uhd_info.get('product', 'USRP'),
+ ser=uhd_info.get('serial')
+ ))
+ print('--- This will take some time. Better grab a cup of tea.')
+ args_str = uhd_info['args']
+ env['_UHD_TEST_ARGS_STR'] = args_str
+ logfile_name = "log{}.log".format(
+ args_str.replace('type=', '_').replace('serial=', '_').replace(',', '')
+ )
+ resultsfile_name = "results{}.log".format(
+ args_str.replace('type=', '_').replace('serial=', '_').replace(',', '')
+ )
+ env['_UHD_TEST_LOGFILE'] = os.path.join(args.log_dir, logfile_name)
+ env['_UHD_TEST_RESULTSFILE'] = os.path.join(args.log_dir, resultsfile_name)
+ env['_UHD_TEST_LOG_LEVEL'] = str(logging.INFO)
+ env['_UHD_TEST_PRINT_LEVEL'] = str(logging.WARNING)
+ proc = subprocess.Popen(
+ [
+ "python", "-m", "unittest", "discover", "-v",
+ "-s", args.src_dir,
+ "-p", devtest_pattern,
+ ],
+ env=env,
+ stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+ )
+ print(proc.communicate()[0])
+ if proc.returncode != 0:
+ tests_passed = False
+ print('--- Done testing all attached devices.')
+ return tests_passed
+
+if __name__ == "__main__":
+ exit(not main())
+
diff --git a/host/tests/devtest/rx_samples_to_file_test.py b/host/tests/devtest/rx_samples_to_file_test.py
new file mode 100755
index 000000000..bac6ac256
--- /dev/null
+++ b/host/tests/devtest/rx_samples_to_file_test.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Test the rx_samples_to_file example. """
+
+from uhd_test_base import uhd_example_test_case
+
+class rx_samples_to_file_test(uhd_example_test_case):
+ """
+ Run rx_samples_to_file and check output.
+ """
+ tests = {
+ 'default': {
+ 'duration': 1,
+ 'rate': 5e6,
+ },
+ }
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = rx_samples_to_file_test.tests
+
+ def run_test(self, test_name, test_args):
+ """
+ Test launcher. Runs the example.
+ """
+ self.log.info('Running test {n}, Subdev = {subdev}, Sample Rate = {rate}'.format(
+ n=test_name, subdev=test_args.get('subdev'), rate=test_args.get('rate'),
+ ))
+ # Run example:
+ args = [
+ self.create_addr_args_str(),
+ '--null',
+ '--stats',
+ '--duration', str(test_args['duration']),
+ '--rate', str(test_args.get('rate', 1e6)),
+ '--wirefmt', test_args.get('wirefmt', 'sc16'),
+ ]
+ if test_args.has_key('subdev'):
+ args.append('--subdev')
+ args.append(test_args['subdev'])
+ (app, run_results) = self.run_example('rx_samples_to_file', args)
+ # Evaluate pass/fail:
+ run_results['passed'] = all([
+ not run_results['has_D'],
+ not run_results['has_S'],
+ run_results['return_code'] == 0,
+ ])
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/devtest/test_messages_test.py b/host/tests/devtest/test_messages_test.py
new file mode 100644
index 000000000..496765c75
--- /dev/null
+++ b/host/tests/devtest/test_messages_test.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Test the test_messages example. """
+
+import re
+from uhd_test_base import uhd_example_test_case
+
+class uhd_test_messages_test(uhd_example_test_case):
+ """
+ Run test_messages and check output.
+ """
+ tests = {'default': {},}
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = uhd_test_messages_test.tests
+
+ def run_test(self, test_name, test_args):
+ """ Run the app and scrape for the failure messages. """
+ self.log.info('Running test {n}'.format(n=test_name,))
+ # Run example:
+ args = [
+ self.create_addr_args_str(),
+ ]
+ if test_args.has_key('ntests'):
+ args.append('--ntests')
+ args.append(test_args['ntests'])
+ (app, run_results) = self.run_example('test_messages', args)
+ # Evaluate pass/fail:
+ succ_fail_re = re.compile(r'(?P<test>.*)->\s+(?P<succ>\d+) successes,\s+(?P<fail>\d+) +failures')
+ for mo in succ_fail_re.finditer(app.stdout):
+ key = mo.group("test").strip().replace(' ', '_').lower()
+ successes = int(mo.group("succ"))
+ failures = int(mo.group("fail"))
+ run_results[key] = "{}/{}".format(successes, successes+failures)
+ run_results['passed'] = bool(failures)
+
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/devtest/test_pps_test.py b/host/tests/devtest/test_pps_test.py
new file mode 100755
index 000000000..1e5b36e2c
--- /dev/null
+++ b/host/tests/devtest/test_pps_test.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Test for test_pps_input. """
+
+import re
+from uhd_test_base import uhd_example_test_case
+
+class uhd_test_pps_test(uhd_example_test_case):
+ """ Run test_pps_input. """
+ tests = {'default': {},}
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = uhd_test_pps_test.tests
+
+ def run_test(self, test_name, test_args):
+ """ Run the app and scrape for the success message. """
+ self.log.info('Running test {n}'.format(n=test_name,))
+ # Run example:
+ args = [
+ self.create_addr_args_str(),
+ ]
+ if test_args.has_key('source'):
+ args.append('--source')
+ args.append(test_args['source'])
+ (app, run_results) = self.run_example('test_pps_input', args)
+ # Evaluate pass/fail:
+ run_results['passed'] = all([
+ app.returncode == 0,
+ re.search('Success!', app.stdout) is not None,
+ ])
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/devtest/tx_bursts_test.py b/host/tests/devtest/tx_bursts_test.py
new file mode 100755
index 000000000..863f35fe1
--- /dev/null
+++ b/host/tests/devtest/tx_bursts_test.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Run the test for tx_burst """
+
+import re
+from uhd_test_base import uhd_example_test_case
+
+class uhd_tx_bursts_test(uhd_example_test_case):
+ """ Run test_messages. """
+ tests = {
+ 'default': {
+ 'nsamps': 10000,
+ 'rate': 5e6,
+ 'channels': '0',
+ },
+ }
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = uhd_tx_bursts_test.tests
+
+ def run_test(self, test_name, test_args):
+ """ Run the app and scrape for the failure messages. """
+ self.log.info('Running test {name}, Channel = {channel}, Sample Rate = {rate}'.format(
+ name=test_name, channel=test_args.get('channel'), rate=test_args.get('rate'),
+ ))
+ # Run example:
+ args = [
+ self.create_addr_args_str(),
+ '--nsamps', str(test_args['nsamps']),
+ '--channels', str(test_args['channels']),
+ '--rate', str(test_args.get('rate', 1e6)),
+ ]
+ if test_args.has_key('subdev'):
+ args.append('--subdev')
+ args.append(test_args['subdev'])
+ (app, run_results) = self.run_example('tx_bursts', args)
+ # Evaluate pass/fail:
+ run_results['passed'] = all([
+ app.returncode == 0,
+ not run_results['has_S'],
+ ])
+ run_results['async_burst_ack_found'] = re.search('success', app.stdout) is not None
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/devtest/uhd_test_base.py b/host/tests/devtest/uhd_test_base.py
new file mode 100755
index 000000000..07a95ae3d
--- /dev/null
+++ b/host/tests/devtest/uhd_test_base.py
@@ -0,0 +1,222 @@
+#!/usr/bin/env python
+
+import os
+import sys
+import yaml
+import unittest
+import re
+import time
+import logging
+from subprocess import Popen, PIPE, STDOUT
+from usrp_probe import get_usrp_list
+
+#--------------------------------------------------------------------------
+# Application
+#--------------------------------------------------------------------------
+class shell_application(object):
+ """
+ Wrapper for applications that are in $PATH.
+ Note: The CMake infrastructure makes sure all examples and utils are in $PATH.
+ """
+ def __init__(self, name):
+ self.name = name
+ self.stdout = ''
+ self.stderr = ''
+ self.returncode = None
+ self.exec_time = None
+
+ def run(self, args = []):
+ cmd_line = [self.name]
+ cmd_line.extend(args)
+ start_time = time.time()
+ p = Popen(cmd_line, stdout=PIPE, stderr=PIPE, close_fds=True)
+ self.stdout, self.stderr = p.communicate()
+ self.returncode = p.returncode
+ self.exec_time = time.time() - start_time
+
+#--------------------------------------------------------------------------
+# Test case base
+#--------------------------------------------------------------------------
+class uhd_test_case(unittest.TestCase):
+ """
+ Base class for UHD test cases.
+ """
+ test_name = '--TEST--'
+
+ def set_up(self):
+ """
+ Override this to add own setup code per test.
+ """
+ pass
+
+ def setUp(self):
+ self.name = self.__class__.__name__
+ self.test_id = self.id().split('.')[-1]
+ self.results = {}
+ self.results_file = os.getenv('_UHD_TEST_RESULTSFILE', "")
+ if self.results_file and os.path.isfile(self.results_file):
+ self.results = yaml.safe_load(open(self.results_file).read()) or {}
+ self.args_str = os.getenv('_UHD_TEST_ARGS_STR', "")
+ self.usrp_info = get_usrp_list(self.args_str)[0]
+ if not self.results.has_key(self.usrp_info['serial']):
+ self.results[self.usrp_info['serial']] = {}
+ if not self.results[self.usrp_info['serial']].has_key(self.name):
+ self.results[self.usrp_info['serial']][self.name] = {}
+ self.setup_logger()
+ self.set_up()
+
+ def setup_logger(self):
+ " Add logging infrastructure "
+ self.log = logging.getLogger("devtest.{name}".format(name=self.name))
+ self.log_file = os.getenv('_UHD_TEST_LOGFILE', "devtest.log")
+ #self.log_level = int(os.getenv('_UHD_TEST_LOG_LEVEL', logging.DEBUG))
+ #self.print_level = int(os.getenv('_UHD_TEST_PRINT_LEVEL', logging.WARNING))
+ self.log_level = logging.DEBUG
+ self.print_level = logging.WARNING
+ file_handler = logging.FileHandler(self.log_file)
+ file_handler.setLevel(self.log_level)
+ console_handler = logging.StreamHandler()
+ console_handler.setLevel(self.print_level)
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+ file_handler.setFormatter(formatter)
+ console_handler.setFormatter(formatter)
+ self.log.setLevel(logging.DEBUG)
+ self.log.addHandler(file_handler)
+ self.log.addHandler(console_handler)
+ self.log.info("Starting test with device: {dev}".format(dev=self.args_str))
+
+ def tear_down(self):
+ pass
+
+ def tearDown(self):
+ self.tear_down()
+ if self.results_file:
+ open(self.results_file, 'w').write(yaml.dump(self.results, default_flow_style=False))
+
+ def report_result(self, testname, key, value):
+ """ Store a result as a key/value pair.
+ After completion, all results for one test are written to the results file.
+ """
+ if not self.results[self.usrp_info['serial']][self.name].has_key(testname):
+ self.results[self.usrp_info['serial']][self.name][testname] = {}
+ self.results[self.usrp_info['serial']][self.name][testname][key] = value
+
+ def create_addr_args_str(self, argname="args"):
+ """ Returns an args string, usually '--args "type=XXX,serial=YYY" """
+ if len(self.args_str) == 0:
+ return ''
+ return '--{}={}'.format(argname, self.args_str)
+
+ def filter_warnings(self, errstr):
+ """ Searches errstr for UHD warnings, removes them, and puts them into a separate string.
+ Returns (errstr, warnstr), where errstr no longer has warning. """
+ warn_re = re.compile("UHD Warning:\n(?: .*\n)+")
+ warnstr = "\n".join(warn_re.findall(errstr)).strip()
+ errstr = warn_re.sub('', errstr).strip()
+ return (errstr, warnstr)
+
+ def filter_stderr(self, stderr, run_results={}):
+ """ Filters the output to stderr. run_results[] is a dictionary.
+ This function will:
+ - Remove UUUUU... strings, since they are generally not a problem.
+ - Remove all DDDD and SSSS strings, and add run_results['has_S'] = True
+ and run_results['has_D'] = True.
+ - Remove warnings and put them in run_results['warnings']
+ - Put the filtered error string into run_results['errors'] and returns the dictionary
+ """
+ errstr, run_results['warnings'] = self.filter_warnings(stderr)
+ # Scan for underruns and sequence errors / dropped packets not detected in the counter
+ errstr = re.sub('UU+', '', errstr)
+ (errstr, n_subs) = re.subn('SS+', '', errstr)
+ if n_subs:
+ run_results['has_S'] = True
+ (errstr, n_subs) = re.subn('DD+', '', errstr)
+ if n_subs:
+ run_results['has_D'] = True
+ errstr = re.sub("\n\n+", "\n", errstr)
+ run_results['errors'] = errstr.strip()
+ return run_results
+
+class uhd_example_test_case(uhd_test_case):
+ """
+ A test case that runs an example.
+ """
+
+ def setup_example(self):
+ """
+ Override this to add specific setup code.
+ """
+ pass
+
+ def set_up(self):
+ """
+ """
+ self.setup_example()
+
+ def run_test(self, test_name, test_args):
+ """
+ Override this to run the actual example.
+
+ Needs to return either a boolean or a dict with key 'passed' to determine
+ pass/fail.
+ """
+ raise NotImplementedError
+
+ def run_example(self, example, args):
+ """
+ Run `example' (which has to be a UHD example or utility) with `args'.
+ Return results and the app object.
+ """
+ self.log.info("Running example: `{example} {args}'".format(example=example, args=" ".join(args)))
+ app = shell_application(example)
+ app.run(args)
+ run_results = {
+ 'return_code': app.returncode,
+ 'passed': False,
+ 'has_D': False,
+ 'has_S': False,
+ }
+ run_results = self.filter_stderr(app.stderr, run_results)
+ self.log.info('STDERR Output:')
+ self.log.info(str(app.stderr))
+ return (app, run_results)
+
+
+ def report_example_results(self, test_name, run_results):
+ for key in sorted(run_results):
+ self.log.info('{key} = {val}'.format(key=key, val=run_results[key]))
+ self.report_result(
+ test_name,
+ key, run_results[key]
+ )
+ if run_results.has_key('passed'):
+ self.report_result(
+ test_name,
+ 'status',
+ 'Passed' if run_results['passed'] else 'Failed',
+ )
+ if run_results.has_key('errors'):
+ self.report_result(
+ test_name,
+ 'errors',
+ 'Yes' if run_results['errors'] else 'No',
+ )
+
+ def test_all(self):
+ """
+ Hook for test runner. Needs to be a class method that starts with 'test'.
+ Calls run_test().
+ """
+ for test_name, test_args in self.test_params.iteritems():
+ if not test_args.has_key('products') or (self.usrp_info['product'] in test_args.get('products', [])):
+ run_results = self.run_test(test_name, test_args)
+ passed = bool(run_results)
+ if isinstance(run_results, dict):
+ passed = run_results['passed']
+ self.assertTrue(
+ passed,
+ msg="Errors occurred during test `{t}'. Check log file for details.\nRun results:\n{r}".format(
+ t=test_name, r=yaml.dump(run_results, default_flow_style=False)
+ )
+ )
+
diff --git a/host/tests/devtest/usrp_probe.py b/host/tests/devtest/usrp_probe.py
new file mode 100644
index 000000000..50d484518
--- /dev/null
+++ b/host/tests/devtest/usrp_probe.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+#
+# Copyright 2015-2016 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Run uhd_find_devices and parse the output. """
+
+import re
+import subprocess
+
+def get_usrp_list(device_filter=None, env=None):
+ """ Returns a list of dicts that contain USRP info """
+ try:
+ cmd = ['uhd_find_devices']
+ if device_filter is not None:
+ cmd += ['--args', device_filter]
+ output = subprocess.check_output(cmd, env=env)
+ except subprocess.CalledProcessError:
+ return []
+ split_re = "\n*-+\n-- .*\n-+\n"
+ uhd_strings = re.split(split_re, output)
+ result = []
+ for uhd_string in uhd_strings:
+ if not re.match("Device Address", uhd_string):
+ continue
+ this_result = {k: v for k, v in re.findall(" ([a-z]+): (.*)", uhd_string)}
+ args_string = ""
+ try:
+ args_string = "type={},serial={}".format(this_result['type'], this_result['serial'])
+ except KeyError:
+ continue
+ this_result['args'] = args_string
+ result.append(this_result)
+ return result
+
+if __name__ == "__main__":
+ print get_usrp_list()
+ print get_usrp_list('type=x300')
diff --git a/host/tests/devtest/usrp_probe_test.py b/host/tests/devtest/usrp_probe_test.py
new file mode 100755
index 000000000..a136a2af7
--- /dev/null
+++ b/host/tests/devtest/usrp_probe_test.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+#
+# Copyright 2015 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+""" Run the test for tx_burst """
+
+import re
+from uhd_test_base import uhd_example_test_case
+
+class uhd_usrp_probe_test(uhd_example_test_case):
+ """ Run uhd_usrp_probe """
+ tests = {
+ 'default': {
+ 'init-only': False,
+ },
+ }
+
+ def setup_example(self):
+ """
+ Set args.
+ """
+ self.test_params = uhd_usrp_probe_test.tests
+
+ def run_test(self, test_name, test_args):
+ """ Run the app and scrape for the failure messages. """
+ self.log.info('Running test {name}'.format(name=test_name))
+ # Run example:
+ args = [
+ self.create_addr_args_str(),
+ ]
+ if test_args.get('init-only'):
+ args.append('--init-only')
+ (app, run_results) = self.run_example('uhd_usrp_probe', args)
+ # Evaluate pass/fail:
+ run_results['passed'] = all([
+ app.returncode == 0,
+ ])
+ self.report_example_results(test_name, run_results)
+ return run_results
+
diff --git a/host/tests/expert_test.cpp b/host/tests/expert_test.cpp
new file mode 100644
index 000000000..016761194
--- /dev/null
+++ b/host/tests/expert_test.cpp
@@ -0,0 +1,256 @@
+//
+// Copyright 2010-2011 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <boost/test/unit_test.hpp>
+#include <boost/format.hpp>
+#include <boost/make_shared.hpp>
+#include "../lib/experts/expert_container.hpp"
+#include "../lib/experts/expert_factory.hpp"
+#include <uhd/property_tree.hpp>
+#include <fstream>
+
+using namespace uhd::experts;
+
+class worker1_t : public worker_node_t {
+public:
+ worker1_t(const node_retriever_t& db)
+ : worker_node_t("A+B=C"), _a(db, "A/desired"), _b(db, "B"), _c(db, "C")
+ {
+ bind_accessor(_a);
+ bind_accessor(_b);
+ bind_accessor(_c);
+ }
+
+private:
+ void resolve() {
+ _c = _a + _b;
+ }
+
+ data_reader_t<int> _a;
+ data_reader_t<int> _b;
+ data_writer_t<int> _c;
+};
+
+//=============================================================================
+
+class worker2_t : public worker_node_t {
+public:
+ worker2_t(const node_retriever_t& db)
+ : worker_node_t("C*D=E"), _c(db, "C"), _d(db, "D"), _e(db, "E")
+ {
+ bind_accessor(_c);
+ bind_accessor(_d);
+ bind_accessor(_e);
+ }
+
+private:
+ void resolve() {
+ _e.set(_c.get() * _d.get());
+ }
+
+ data_reader_t<int> _c;
+ data_reader_t<int> _d;
+ data_writer_t<int> _e;
+};
+
+//=============================================================================
+
+class worker3_t : public worker_node_t {
+public:
+ worker3_t(const node_retriever_t& db)
+ : worker_node_t("-B=F"), _b(db, "B"), _f(db, "F")
+ {
+ bind_accessor(_b);
+ bind_accessor(_f);
+ }
+
+private:
+ void resolve() {
+ _f.set(-_b.get());
+ }
+
+ data_reader_t<int> _b;
+ data_writer_t<int> _f;
+};
+
+//=============================================================================
+
+class worker4_t : public worker_node_t {
+public:
+ worker4_t(const node_retriever_t& db)
+ : worker_node_t("E-F=G"), _e(db, "E"), _f(db, "F"), _g(db, "G")
+ {
+ bind_accessor(_e);
+ bind_accessor(_f);
+ bind_accessor(_g);
+ }
+
+private:
+ void resolve() {
+ _g.set(_e.get() - _f.get());
+ }
+
+ data_reader_t<int> _e;
+ data_reader_t<int> _f;
+ data_writer_t<int> _g;
+};
+
+//=============================================================================
+
+class worker5_t : public worker_node_t {
+public:
+ worker5_t(const node_retriever_t& db, boost::shared_ptr<int> output)
+ : worker_node_t("Consume_G"), _g(db, "G"), _c(db, "C"), _output(output)
+ {
+ bind_accessor(_g);
+// bind_accessor(_c);
+ }
+
+private:
+ void resolve() {
+ *_output = _g;
+ }
+
+ data_reader_t<int> _g;
+ data_writer_t<int> _c;
+
+ boost::shared_ptr<int> _output;
+};
+
+class worker6_t : public worker_node_t {
+public:
+ worker6_t() : worker_node_t("null_worker")
+ {
+ }
+
+private:
+ void resolve() {
+ }
+};
+
+//=============================================================================
+
+#define DUMP_VARS \
+ BOOST_TEST_MESSAGE( str(boost::format("### State = {A=%d%s, B=%d%s, C=%d%s, D=%d%s, E=%d%s, F=%d%s, G=%d%s}\n") % \
+ nodeA.get() % (nodeA.is_dirty()?"*":"") % \
+ nodeB.get() % (nodeB.is_dirty()?"*":"") % \
+ nodeC.get() % (nodeC.is_dirty()?"*":"") % \
+ nodeD.get() % (nodeD.is_dirty()?"*":"") % \
+ nodeE.get() % (nodeE.is_dirty()?"*":"") % \
+ nodeF.get() % (nodeF.is_dirty()?"*":"") % \
+ nodeG.get() % (nodeG.is_dirty()?"*":"")) );
+
+#define VALIDATE_ALL_DEPENDENCIES \
+ BOOST_CHECK(!nodeA.is_dirty()); \
+ BOOST_CHECK(!nodeB.is_dirty()); \
+ BOOST_CHECK(!nodeC.is_dirty()); \
+ BOOST_CHECK(!nodeD.is_dirty()); \
+ BOOST_CHECK(!nodeE.is_dirty()); \
+ BOOST_CHECK(!nodeF.is_dirty()); \
+ BOOST_CHECK(!nodeG.is_dirty()); \
+ BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get()); \
+ BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get()); \
+ BOOST_CHECK(nodeF.get() == - nodeB.get()); \
+ BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get()); \
+ BOOST_CHECK(nodeG.get() == *final_output);
+
+
+BOOST_AUTO_TEST_CASE(test_experts){
+ //Initialize container object
+ expert_container::sptr container = expert_factory::create_container("example");
+ uhd::property_tree::sptr tree = uhd::property_tree::make();
+
+ //Output of expert tree
+ boost::shared_ptr<int> final_output = boost::make_shared<int>();
+
+ //Add data nodes to container
+ expert_factory::add_dual_prop_node<int>(container, tree, "A", 0, uhd::experts::AUTO_RESOLVE_ON_WRITE);
+ expert_factory::add_prop_node<int>(container, tree, "B", 0);
+ expert_factory::add_data_node<int>(container, "C", 0);
+ expert_factory::add_data_node<int>(container, "D", 1);
+ expert_factory::add_prop_node<int>(container, tree, "E", 0, uhd::experts::AUTO_RESOLVE_ON_READ);
+ expert_factory::add_data_node<int>(container, "F", 0);
+ expert_factory::add_data_node<int>(container, "G", 0);
+
+ //Add worker nodes to container
+ expert_factory::add_worker_node<worker1_t>(container, container->node_retriever());
+ expert_factory::add_worker_node<worker2_t>(container, container->node_retriever());
+ expert_factory::add_worker_node<worker3_t>(container, container->node_retriever());
+ expert_factory::add_worker_node<worker4_t>(container, container->node_retriever());
+ expert_factory::add_worker_node<worker5_t>(container, container->node_retriever(), final_output);
+ expert_factory::add_worker_node<worker6_t>(container);
+
+ //Once initialized, getting modify access to graph nodes is possible (by design) but extremely red-flaggy!
+ //But we do it here to monitor things
+ data_node_t<int>& nodeA = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("A/desired"))));
+ data_node_t<int>& nodeB = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("B"))));
+ data_node_t<int>& nodeC = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("C"))));
+ data_node_t<int>& nodeD = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("D"))));
+ data_node_t<int>& nodeE = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("E"))));
+ data_node_t<int>& nodeF = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("F"))));
+ data_node_t<int>& nodeG = *(const_cast< data_node_t<int>* >(dynamic_cast< const data_node_t<int>* >(&container->node_retriever().lookup("G"))));
+
+ DUMP_VARS
+
+ //Ensure init behavior
+ BOOST_CHECK(nodeA.is_dirty());
+ BOOST_CHECK(nodeB.is_dirty());
+ BOOST_CHECK(nodeC.is_dirty());
+ BOOST_CHECK(nodeD.is_dirty());
+ BOOST_CHECK(nodeE.is_dirty());
+ BOOST_CHECK(nodeF.is_dirty());
+ BOOST_CHECK(nodeG.is_dirty());
+ container->resolve_all();
+ VALIDATE_ALL_DEPENDENCIES //Ensure a default resolve
+
+ //Ensure basic node value propagation
+ tree->access<int>("B").set(3);
+ BOOST_CHECK(nodeB.get() == 3); //Ensure value propagated
+ BOOST_CHECK(nodeB.is_dirty()); //Ensure that nothing got resolved...
+ container->resolve_all();
+ VALIDATE_ALL_DEPENDENCIES
+
+ nodeD.set(2); //Hack for testing
+
+ //Ensure auto-resolve on write
+ tree->access<int>("A").set(200);
+ BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get());
+ BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get());
+ BOOST_CHECK(nodeG.get() == nodeE.get() - nodeF.get());
+ container->resolve_all();
+ VALIDATE_ALL_DEPENDENCIES
+
+ container->resolve_all();
+ VALIDATE_ALL_DEPENDENCIES
+
+ //Ensure auto-resolve on read
+ tree->access<int>("E").get();
+ BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get());
+ BOOST_CHECK(nodeE.get() == nodeC.get() * nodeD.get());
+ BOOST_CHECK(!nodeE.is_dirty());
+ tree->access<int>("E").set(-10);
+ container->resolve_all(true);
+ VALIDATE_ALL_DEPENDENCIES
+
+ //Resolve to/from
+ tree->access<int>("A").set(-1);
+ container->resolve_to("C");
+ BOOST_CHECK(nodeC.get() == nodeA.get() + nodeB.get());
+ BOOST_CHECK(!nodeC.is_dirty());
+ container->resolve_to("Consume_G");
+ VALIDATE_ALL_DEPENDENCIES
+}
diff --git a/host/tests/fe_conn_test.cpp b/host/tests/fe_conn_test.cpp
new file mode 100644
index 000000000..b8e69816a
--- /dev/null
+++ b/host/tests/fe_conn_test.cpp
@@ -0,0 +1,108 @@
+//
+// Copyright 2016 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <uhd/usrp/fe_connection.hpp>
+#include <uhd/exception.hpp>
+#include <boost/test/unit_test.hpp>
+
+using namespace uhd::usrp;
+
+BOOST_AUTO_TEST_CASE(test_quardrature){
+ fe_connection_t IQ("IQ"), QI("QI"), IbQ("IbQ"), QbI("QbI"), QbIb("QbIb");
+ BOOST_CHECK(IQ.get_sampling_mode()==fe_connection_t::QUADRATURE);
+ BOOST_CHECK(QI.get_sampling_mode()==fe_connection_t::QUADRATURE);
+ BOOST_CHECK(IbQ.get_sampling_mode()==fe_connection_t::QUADRATURE);
+ BOOST_CHECK(QbI.get_sampling_mode()==fe_connection_t::QUADRATURE);
+ BOOST_CHECK(QbIb.get_sampling_mode()==fe_connection_t::QUADRATURE);
+
+ BOOST_CHECK(not IQ.is_iq_swapped());
+ BOOST_CHECK(QI.is_iq_swapped());
+ BOOST_CHECK(not IbQ.is_iq_swapped());
+ BOOST_CHECK(QbI.is_iq_swapped());
+ BOOST_CHECK(QbIb.is_iq_swapped());
+
+ BOOST_CHECK(not IQ.is_i_inverted());
+ BOOST_CHECK(not QI.is_i_inverted());
+ BOOST_CHECK(IbQ.is_i_inverted());
+ BOOST_CHECK(not QbI.is_i_inverted());
+ BOOST_CHECK(QbIb.is_i_inverted());
+
+ BOOST_CHECK(not IQ.is_q_inverted());
+ BOOST_CHECK(not QI.is_q_inverted());
+ BOOST_CHECK(not IbQ.is_q_inverted());
+ BOOST_CHECK(QbI.is_q_inverted());
+ BOOST_CHECK(QbIb.is_q_inverted());
+}
+
+BOOST_AUTO_TEST_CASE(test_heterodyne){
+ fe_connection_t II("II"), QQ("QQ"), IbIb("IbIb"), QbQb("QbQb");
+ BOOST_CHECK(II.get_sampling_mode()==fe_connection_t::HETERODYNE);
+ BOOST_CHECK(QQ.get_sampling_mode()==fe_connection_t::HETERODYNE);
+ BOOST_CHECK(IbIb.get_sampling_mode()==fe_connection_t::HETERODYNE);
+ BOOST_CHECK(QbQb.get_sampling_mode()==fe_connection_t::HETERODYNE);
+
+ BOOST_CHECK(not II.is_iq_swapped());
+ BOOST_CHECK(QQ.is_iq_swapped());
+ BOOST_CHECK(not IbIb.is_iq_swapped());
+ BOOST_CHECK(QbQb.is_iq_swapped());
+
+ BOOST_CHECK(not II.is_i_inverted());
+ BOOST_CHECK(not QQ.is_i_inverted());
+ BOOST_CHECK(IbIb.is_i_inverted());
+ BOOST_CHECK(QbQb.is_i_inverted());
+
+ BOOST_CHECK(not II.is_q_inverted());
+ BOOST_CHECK(not QQ.is_q_inverted());
+ BOOST_CHECK(IbIb.is_q_inverted());
+ BOOST_CHECK(QbQb.is_q_inverted());
+
+ BOOST_CHECK_THROW(fe_connection_t dummy("IIb"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("IbI"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("QQb"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("QbQ"), uhd::value_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_real){
+ fe_connection_t I("I"), Q("Q"), Ib("Ib"), Qb("Qb");
+ BOOST_CHECK(I.get_sampling_mode()==fe_connection_t::REAL);
+ BOOST_CHECK(Q.get_sampling_mode()==fe_connection_t::REAL);
+ BOOST_CHECK(Ib.get_sampling_mode()==fe_connection_t::REAL);
+ BOOST_CHECK(Qb.get_sampling_mode()==fe_connection_t::REAL);
+
+ BOOST_CHECK(not I.is_iq_swapped());
+ BOOST_CHECK(Q.is_iq_swapped());
+ BOOST_CHECK(not Ib.is_iq_swapped());
+ BOOST_CHECK(Qb.is_iq_swapped());
+
+ BOOST_CHECK(not I.is_i_inverted());
+ BOOST_CHECK(not Q.is_i_inverted());
+ BOOST_CHECK(Ib.is_i_inverted());
+ BOOST_CHECK(Qb.is_i_inverted());
+
+ BOOST_CHECK(not I.is_q_inverted());
+ BOOST_CHECK(not Q.is_q_inverted());
+ BOOST_CHECK(not Ib.is_q_inverted());
+ BOOST_CHECK(not Qb.is_q_inverted());
+}
+
+BOOST_AUTO_TEST_CASE(test_invalid){
+ BOOST_CHECK_THROW(fe_connection_t dummy("blah"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("123456"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("ii"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("qb"), uhd::value_error);
+ BOOST_CHECK_THROW(fe_connection_t dummy("IIIQ"), uhd::value_error);
+}
diff --git a/host/tests/graph.hpp b/host/tests/graph.hpp
new file mode 100644
index 000000000..0c2be0347
--- /dev/null
+++ b/host/tests/graph.hpp
@@ -0,0 +1,51 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#ifndef INCLUDED_TEST_GRAPH_HPP
+#define INCLUDED_TEST_GRAPH_HPP
+
+#include <uhd/rfnoc/node_ctrl_base.hpp>
+#include <uhd/rfnoc/sink_node_ctrl.hpp>
+#include <uhd/rfnoc/source_node_ctrl.hpp>
+
+#define MAKE_NODE(name) test_node::sptr name(new test_node(#name));
+
+// Smallest possible test class
+class test_node : virtual public uhd::rfnoc::sink_node_ctrl, virtual public uhd::rfnoc::source_node_ctrl
+{
+public:
+ typedef boost::shared_ptr<test_node> sptr;
+
+ test_node(const std::string &test_id) : _test_id(test_id) {};
+
+ void issue_stream_cmd(const uhd::stream_cmd_t &, const size_t) {/* nop */};
+
+ std::string get_test_id() const { return _test_id; };
+
+private:
+ const std::string _test_id;
+
+}; /* class test_node */
+
+void connect_nodes(uhd::rfnoc::source_node_ctrl::sptr A, uhd::rfnoc::sink_node_ctrl::sptr B)
+{
+ A->connect_downstream(B);
+ B->connect_upstream(A);
+}
+
+#endif /* INCLUDED_TEST_GRAPH_HPP */
+// vim: sw=4 et:
diff --git a/host/tests/graph_search_test.cpp b/host/tests/graph_search_test.cpp
new file mode 100644
index 000000000..4106d724e
--- /dev/null
+++ b/host/tests/graph_search_test.cpp
@@ -0,0 +1,169 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "graph.hpp"
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::rfnoc;
+
+// test class derived, this is what we search for
+class result_node : public test_node
+{
+public:
+ typedef boost::shared_ptr<result_node> sptr;
+
+ result_node(const std::string &test_id) : test_node(test_id) {};
+
+}; /* class result_node */
+
+#define MAKE_RESULT_NODE(name) result_node::sptr name(new result_node(#name));
+
+BOOST_AUTO_TEST_CASE(test_simplest_downstream_search)
+{
+ MAKE_NODE(node_A);
+ MAKE_NODE(node_B);
+
+ // Simplest possible scenario: Connect B downstream of A and let
+ // A find B
+ connect_nodes(node_A, node_B);
+
+ test_node::sptr result = node_A->find_downstream_node<test_node>()[0];
+ BOOST_REQUIRE(result);
+ BOOST_CHECK_EQUAL(result->get_test_id(), "node_B");
+}
+
+BOOST_AUTO_TEST_CASE(test_simple_downstream_search)
+{
+ MAKE_NODE(node_A);
+ MAKE_NODE(node_B0);
+ MAKE_NODE(node_B1);
+
+ // Simple scenario: Connect both B{1,2} downstream of A and let
+ // it find them
+ connect_nodes(node_A, node_B0);
+ connect_nodes(node_A, node_B1);
+
+ // We're still searching for test_node, so any downstream block will match
+ std::vector< test_node::sptr > result = node_A->find_downstream_node<test_node>();
+ BOOST_REQUIRE(result.size() == 2);
+ BOOST_CHECK(
+ (result[0]->get_test_id() == "node_B0" and result[1]->get_test_id() == "node_B1") or
+ (result[1]->get_test_id() == "node_B0" and result[0]->get_test_id() == "node_B1")
+ );
+ BOOST_CHECK(result[0] == node_B0 or result[0] == node_B1);
+}
+
+BOOST_AUTO_TEST_CASE(test_linear_downstream_search)
+{
+ MAKE_NODE(node_A);
+ MAKE_RESULT_NODE(node_B);
+ MAKE_RESULT_NODE(node_C);
+
+ // Slightly more complex graph:
+ connect_nodes(node_A, node_B);
+ connect_nodes(node_B, node_C);
+
+ // This time, we search for result_node
+ std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>();
+ std::cout << "size: " << result.size() << std::endl;
+ BOOST_CHECK_EQUAL(result.size(), 1);
+ BOOST_CHECK_EQUAL(result[0]->get_test_id(), "node_B");
+ BOOST_FOREACH(const result_node::sptr &node, result) {
+ std::cout << node->get_test_id() << std::endl;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_iter_downstream_search)
+{
+ MAKE_NODE(node_A);
+ MAKE_NODE(node_B0);
+ MAKE_NODE(node_B1);
+ MAKE_NODE(node_C0);
+ MAKE_RESULT_NODE(node_C1);
+ MAKE_RESULT_NODE(node_C2);
+ MAKE_RESULT_NODE(node_C3);
+ MAKE_RESULT_NODE(node_D0);
+
+ // Slightly more complex graph:
+ connect_nodes(node_A, node_B0);
+ connect_nodes(node_A, node_B1);
+ connect_nodes(node_B0, node_C0);
+ connect_nodes(node_B0, node_C1);
+ connect_nodes(node_B1, node_C2);
+ connect_nodes(node_B1, node_C3);
+ connect_nodes(node_C0, node_D0);
+
+ // This time, we search for result_node
+ std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>();
+ BOOST_REQUIRE(result.size() == 4);
+ BOOST_FOREACH(const result_node::sptr &node, result) {
+ std::cout << node->get_test_id() << std::endl;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_iter_cycle_downstream_search)
+{
+ MAKE_NODE(node_A);
+ MAKE_NODE(node_B0);
+ MAKE_NODE(node_B1);
+ MAKE_NODE(node_C0);
+ MAKE_RESULT_NODE(node_C1);
+ MAKE_RESULT_NODE(node_C2);
+ MAKE_RESULT_NODE(node_C3);
+ MAKE_RESULT_NODE(node_D0);
+
+ // Slightly more complex graph:
+ connect_nodes(node_A, node_B0);
+ // This connection goes both ways, causing a cycle
+ connect_nodes(node_A, node_B1); connect_nodes(node_B1, node_A);
+ connect_nodes(node_B0, node_C0);
+ connect_nodes(node_B0, node_C1);
+ connect_nodes(node_B1, node_C2);
+ connect_nodes(node_B1, node_C3);
+ connect_nodes(node_C0, node_D0);
+
+ // This time, we search for result_node
+ std::vector< result_node::sptr > result = node_A->find_downstream_node<result_node>();
+ BOOST_REQUIRE(result.size() == 4);
+ BOOST_FOREACH(const result_node::sptr &node, result) {
+ std::cout << node->get_test_id() << std::endl;
+ }
+}
+
+BOOST_AUTO_TEST_CASE(test_mini_cycle_downstream_and_upstream)
+{
+ MAKE_NODE(node_A);
+ MAKE_NODE(node_B);
+
+ // Connect them in a loop
+ connect_nodes(node_A, node_B); connect_nodes(node_B, node_A);
+
+ std::vector< test_node::sptr > result;
+ result = node_A->find_downstream_node<test_node>();
+ BOOST_REQUIRE_EQUAL(result.size(), 1);
+ BOOST_REQUIRE(result[0] == node_B);
+ result = node_B->find_downstream_node<test_node>();
+ BOOST_REQUIRE_EQUAL(result.size(), 1);
+ BOOST_REQUIRE(result[0] == node_A);
+ result = node_A->find_upstream_node<test_node>();
+ BOOST_REQUIRE_EQUAL(result.size(), 1);
+ BOOST_REQUIRE(result[0] == node_B);
+ result = node_B->find_upstream_node<test_node>();
+ BOOST_REQUIRE_EQUAL(result.size(), 1);
+ BOOST_REQUIRE(result[0] == node_A);
+}
diff --git a/host/tests/nocscript_common.hpp b/host/tests/nocscript_common.hpp
new file mode 100644
index 000000000..7467e05fb
--- /dev/null
+++ b/host/tests/nocscript_common.hpp
@@ -0,0 +1,35 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "../lib/rfnoc/nocscript/expression.hpp"
+#include <boost/assign/list_of.hpp>
+
+using namespace uhd::rfnoc::nocscript;
+
+// Some global defs to make tests easier to write
+expression_function::argtype_list_type one_int_arg = boost::assign::list_of(expression::TYPE_INT);
+expression_function::argtype_list_type two_int_args = boost::assign::list_of(expression::TYPE_INT)(expression::TYPE_INT);
+expression_function::argtype_list_type one_double_arg = boost::assign::list_of(expression::TYPE_DOUBLE);
+expression_function::argtype_list_type two_double_args = boost::assign::list_of(expression::TYPE_DOUBLE)(expression::TYPE_DOUBLE);
+expression_function::argtype_list_type one_bool_arg = boost::assign::list_of(expression::TYPE_BOOL);
+expression_function::argtype_list_type two_bool_args = boost::assign::list_of(expression::TYPE_BOOL)(expression::TYPE_BOOL);
+expression_function::argtype_list_type no_args;
+
+expression_container::expr_list_type empty_arg_list;
+
+#define E(x) expression_literal::make(x)
+
diff --git a/host/tests/nocscript_expr_test.cpp b/host/tests/nocscript_expr_test.cpp
new file mode 100644
index 000000000..f82a613a3
--- /dev/null
+++ b/host/tests/nocscript_expr_test.cpp
@@ -0,0 +1,451 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "../lib/rfnoc/nocscript/function_table.hpp"
+#include <boost/test/unit_test.hpp>
+#include <boost/test/floating_point_comparison.hpp>
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+#include <boost/make_shared.hpp>
+#include <boost/format.hpp>
+#include <algorithm>
+#include <iostream>
+
+#include "nocscript_common.hpp"
+
+// We need this global variable for one of the later tests
+int and_counter = 0;
+
+BOOST_AUTO_TEST_CASE(test_literals)
+{
+ expression_literal literal_int("5", expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(literal_int.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(literal_int.get_int(), 5);
+ BOOST_CHECK_EQUAL(literal_int.to_bool(), true);
+ BOOST_REQUIRE_THROW(literal_int.get_string(), uhd::type_error);
+ BOOST_REQUIRE_THROW(literal_int.get_bool(), uhd::type_error);
+
+ expression_literal literal_int0("0", expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(literal_int0.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(literal_int0.to_bool(), false);
+
+ expression_literal literal_double("2.3", expression::TYPE_DOUBLE);
+ BOOST_CHECK_EQUAL(literal_double.infer_type(), expression::TYPE_DOUBLE);
+ BOOST_CHECK_CLOSE(literal_double.get_double(), 2.3, 0.01);
+ BOOST_CHECK_EQUAL(literal_double.to_bool(), true);
+ BOOST_REQUIRE_THROW(literal_double.get_string(), uhd::type_error);
+ BOOST_REQUIRE_THROW(literal_double.get_bool(), uhd::type_error);
+
+ expression_literal literal_bool(true);
+ BOOST_CHECK_EQUAL(literal_bool.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(literal_bool.get_bool(), true);
+ BOOST_CHECK_EQUAL(literal_bool.to_bool(), true);
+ BOOST_CHECK_EQUAL(literal_bool.eval().get_bool(), true);
+ BOOST_REQUIRE_THROW(literal_bool.get_string(), uhd::type_error);
+ BOOST_REQUIRE_THROW(literal_bool.get_int(), uhd::type_error);
+
+ expression_literal literal_bool_false(false);
+ BOOST_CHECK_EQUAL(literal_bool_false.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(literal_bool_false.get_bool(), false);
+ BOOST_CHECK_EQUAL(literal_bool_false.to_bool(), false);
+ BOOST_REQUIRE_EQUAL(literal_bool_false.eval().get_bool(), false);
+ BOOST_REQUIRE_THROW(literal_bool_false.get_string(), uhd::type_error);
+ BOOST_REQUIRE_THROW(literal_bool_false.get_int(), uhd::type_error);
+
+ expression_literal literal_string("'foo bar'", expression::TYPE_STRING);
+ BOOST_CHECK_EQUAL(literal_string.infer_type(), expression::TYPE_STRING);
+ BOOST_CHECK_EQUAL(literal_string.get_string(), "foo bar");
+ BOOST_REQUIRE_THROW(literal_string.get_bool(), uhd::type_error);
+ BOOST_REQUIRE_THROW(literal_string.get_int(), uhd::type_error);
+
+ expression_literal literal_int_vec("[1, 2, 3]", expression::TYPE_INT_VECTOR);
+ BOOST_CHECK_EQUAL(literal_int_vec.infer_type(), expression::TYPE_INT_VECTOR);
+ std::vector<int> test_data = boost::assign::list_of(1)(2)(3);
+ std::vector<int> result = literal_int_vec.get_int_vector();
+ BOOST_CHECK_EQUAL_COLLECTIONS(test_data.begin(), test_data.end(),
+ result.begin(), result.end());
+ BOOST_REQUIRE_THROW(literal_int_vec.get_bool(), uhd::type_error);
+ BOOST_REQUIRE_THROW(literal_int_vec.get_int(), uhd::type_error);
+}
+
+
+// Need those for the variable testing:
+expression::type_t variable_get_type(const std::string &var_name)
+{
+ if (var_name == "spp") {
+ std::cout << "Returning type for $spp..." << std::endl;
+ return expression::TYPE_INT;
+ }
+ if (var_name == "is_true") {
+ std::cout << "Returning type for $is_true..." << std::endl;
+ return expression::TYPE_BOOL;
+ }
+
+ throw uhd::syntax_error("Cannot infer type (unknown variable)");
+}
+
+expression_literal variable_get_value(const std::string &var_name)
+{
+ if (var_name == "spp") {
+ std::cout << "Returning value for $spp..." << std::endl;
+ return expression_literal(5);
+ }
+ if (var_name == "is_true") {
+ std::cout << "Returning value for $is_true..." << std::endl;
+ return expression_literal(true);
+ }
+
+ throw uhd::syntax_error("Cannot read value (unknown variable)");
+}
+
+BOOST_AUTO_TEST_CASE(test_variables)
+{
+ BOOST_REQUIRE_THROW(
+ expression_variable v_fail(
+ "foo", // Invalid token
+ boost::bind(&variable_get_type, _1), boost::bind(&variable_get_value, _1)
+ ),
+ uhd::assertion_error
+ );
+
+ expression_variable v(
+ "$spp", // The token
+ boost::bind(&variable_get_type, _1), // type-getter
+ boost::bind(&variable_get_value, _1) // value-getter
+ );
+ BOOST_CHECK_EQUAL(v.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(v.eval().get_int(), 5);
+}
+
+BOOST_AUTO_TEST_CASE(test_container)
+{
+ // Create some sub-expressions:
+ expression_literal::sptr l_true = E(true);
+ expression_literal::sptr l_false = E(false);
+ expression_literal::sptr l_int = E(5);
+ BOOST_REQUIRE_EQUAL(l_false->get_bool(), false);
+ BOOST_REQUIRE_EQUAL(l_false->to_bool(), false);
+ expression_variable::sptr l_boolvar = boost::make_shared<expression_variable>(
+ "$is_true",
+ boost::bind(&variable_get_type, _1),
+ boost::bind(&variable_get_value, _1)
+ );
+
+ // This will throw anytime it's evaluated:
+ expression_variable::sptr l_failvar = boost::make_shared<expression_variable>(
+ "$does_not_exist",
+ boost::bind(&variable_get_type, _1),
+ boost::bind(&variable_get_value, _1)
+ );
+
+ expression_container c;
+ std::cout << "One true, OR: " << std::endl;
+ c.add(l_true);
+ c.set_combiner_safe(expression_container::COMBINE_OR);
+ expression_literal ret_val_1 = c.eval();
+ BOOST_CHECK_EQUAL(ret_val_1.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(ret_val_1.eval().get_bool(), true);
+
+ std::cout << std::endl << std::endl << "Two true, one false, OR: " << std::endl;
+ c.add(l_true);
+ c.add(l_false);
+ expression_literal ret_val_2 = c.eval();
+ BOOST_CHECK_EQUAL(ret_val_2.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(ret_val_2.eval().get_bool(), true);
+
+ expression_container c2;
+ c2.add(l_false);
+ c2.add(l_false);
+ c2.set_combiner(expression_container::COMBINE_AND);
+ std::cout << std::endl << std::endl << "Two false, AND: " << std::endl;
+ expression_literal ret_val_3 = c2.eval();
+ BOOST_CHECK_EQUAL(ret_val_3.infer_type(), expression::TYPE_BOOL);
+ BOOST_REQUIRE_EQUAL(ret_val_3.eval().get_bool(), false);
+
+ c2.add(l_failvar);
+ // Will not fail, because l_failvar never gets eval'd:
+ expression_literal ret_val_4 = c2.eval();
+ BOOST_CHECK_EQUAL(ret_val_4.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(ret_val_4.eval().get_bool(), false);
+
+ // Same here:
+ c.add(l_failvar);
+ expression_literal ret_val_5 = c.eval();
+ BOOST_CHECK_EQUAL(ret_val_5.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(ret_val_5.eval().get_bool(), true);
+
+ // Now it'll throw:
+ c.set_combiner(expression_container::COMBINE_ALL);
+ BOOST_REQUIRE_THROW(c.eval(), uhd::syntax_error);
+
+ std::cout << "Checking type inference on ',' sequences: " << std::endl;
+ // Check types match
+ BOOST_CHECK_EQUAL(c2.infer_type(), expression::TYPE_BOOL);
+ expression_container c3;
+ c3.set_combiner(expression_container::COMBINE_ALL);
+ c3.add(l_false);
+ c3.add(l_int);
+ BOOST_CHECK_EQUAL(c3.infer_type(), expression::TYPE_INT);
+}
+
+
+// We'll define two functions here: ADD and XOR. The former shall
+// be defined for INT and DOUBLE
+class functable_mockup_impl : public function_table
+{
+ public:
+ functable_mockup_impl(void) {};
+
+ bool function_exists(const std::string &name) const {
+ return name == "ADD" or name == "XOR" or name == "AND";
+ }
+
+ bool function_exists(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types
+ ) const {
+ if (name == "ADD") {
+ if (arg_types.size() == 2
+ and arg_types[0] == expression::TYPE_DOUBLE
+ and arg_types[1] == expression::TYPE_DOUBLE
+ ) {
+ return true;
+ }
+ if (arg_types.size() == 2
+ and arg_types[0] == expression::TYPE_INT
+ and arg_types[1] == expression::TYPE_INT
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ if (name == "XOR" or name == "AND") {
+ if (arg_types.size() == 2
+ and arg_types[0] == expression::TYPE_BOOL
+ and arg_types[1] == expression::TYPE_BOOL
+ ) {
+ return true;
+ }
+ return false;
+ }
+
+ return false;
+ }
+
+ expression::type_t get_type(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types
+ ) const {
+ if (not function_exists(name, arg_types)) {
+ throw uhd::syntax_error(str(
+ boost::format("[EXPR_TEXT] get_type(): Unknown function: %s, %d arguments")
+ % name % arg_types.size()
+ ));
+ }
+
+ if (name == "XOR" or name == "AND") {
+ return expression::TYPE_BOOL;
+ }
+ if (name == "ADD") {
+ return arg_types[0];
+ }
+ UHD_THROW_INVALID_CODE_PATH();
+ }
+
+ expression_literal eval(
+ const std::string &name,
+ const expression_function::argtype_list_type &arg_types,
+ expression_container::expr_list_type &args
+ ) {
+ if (name == "XOR") {
+ if (arg_types.size() != 2
+ or args.size() != 2
+ or arg_types[0] != expression::TYPE_BOOL
+ or arg_types[1] != expression::TYPE_BOOL
+ or args[0]->infer_type() != expression::TYPE_BOOL
+ or args[1]->infer_type() != expression::TYPE_BOOL
+ ) {
+ throw uhd::syntax_error("eval(): XOR type mismatch");
+ }
+ return expression_literal(bool(
+ args[0]->eval().get_bool() xor args[1]->eval().get_bool()
+ ));
+ }
+
+ if (name == "AND") {
+ if (arg_types.size() != 2
+ or args.size() != 2
+ or arg_types[0] != expression::TYPE_BOOL
+ or arg_types[1] != expression::TYPE_BOOL
+ or args[0]->infer_type() != expression::TYPE_BOOL
+ or args[1]->infer_type() != expression::TYPE_BOOL
+ ) {
+ throw uhd::syntax_error("eval(): AND type mismatch");
+ }
+ std::cout << "Calling AND" << std::endl;
+ and_counter++;
+ return expression_literal(bool(
+ args[0]->eval().get_bool() and args[1]->eval().get_bool()
+ ));
+ }
+
+ if (name == "ADD") {
+ if (args.size() != 2) {
+ throw uhd::syntax_error("eval(): ADD type mismatch");
+ }
+ if ((args[0]->infer_type() == expression::TYPE_INT) and
+ (args[1]->infer_type() == expression::TYPE_INT)) {
+ return expression_literal(int(
+ args[0]->eval().get_int() + args[1]->eval().get_int()
+ ));
+ }
+ else if ((args[0]->infer_type() == expression::TYPE_DOUBLE) and
+ (args[1]->infer_type() == expression::TYPE_DOUBLE)) {
+ return expression_literal(double(
+ args[0]->eval().get_double() + args[1]->eval().get_double()
+ ));
+ }
+ throw uhd::syntax_error("eval(): ADD type mismatch");
+ }
+ throw uhd::syntax_error("eval(): unknown function");
+ }
+
+ // We don't actually need this
+ void register_function(
+ const std::string &,
+ const function_table::function_ptr &,
+ const expression::type_t,
+ const expression_function::argtype_list_type &
+ ) {};
+
+};
+
+
+// The annoying part: Testing the test fixtures
+BOOST_AUTO_TEST_CASE(test_functable_mockup)
+{
+ functable_mockup_impl functable;
+
+ BOOST_CHECK(functable.function_exists("ADD"));
+ BOOST_CHECK(functable.function_exists("XOR"));
+ BOOST_CHECK(not functable.function_exists("FOOBAR"));
+
+ BOOST_CHECK(functable.function_exists("ADD", two_int_args));
+ BOOST_CHECK(functable.function_exists("ADD", two_double_args));
+ BOOST_CHECK(functable.function_exists("XOR", two_bool_args));
+ BOOST_CHECK(not functable.function_exists("ADD", two_bool_args));
+ BOOST_CHECK(not functable.function_exists("ADD", no_args));
+ BOOST_CHECK(not functable.function_exists("XOR", no_args));
+
+ BOOST_CHECK_EQUAL(functable.get_type("ADD", two_int_args), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(functable.get_type("ADD", two_double_args), expression::TYPE_DOUBLE);
+ BOOST_CHECK_EQUAL(functable.get_type("XOR", two_bool_args), expression::TYPE_BOOL);
+
+ expression_container::expr_list_type add_args_int =
+ boost::assign::list_of(E(2))(E(3))
+ ;
+ expression_container::expr_list_type add_args_dbl =
+ boost::assign::list_of
+ (E(2.25))
+ (E(5.0))
+ ;
+ expression_container::expr_list_type xor_args_bool =
+ boost::assign::list_of
+ (E(true))
+ (E(false))
+ ;
+
+ BOOST_CHECK_EQUAL(functable.eval("ADD", two_int_args, add_args_int), expression_literal(5));
+ BOOST_CHECK_EQUAL(functable.eval("ADD", two_double_args, add_args_dbl), expression_literal(7.25));
+ BOOST_CHECK_EQUAL(functable.eval("XOR", two_bool_args, xor_args_bool), expression_literal(true));
+}
+
+BOOST_AUTO_TEST_CASE(test_function_expression)
+{
+ function_table::sptr ft = boost::make_shared<functable_mockup_impl>();
+
+ // Very simple function: ADD(2, 3)
+ expression_function func1("ADD", ft);
+ func1.add(E(2));
+ func1.add(E(3));
+
+ BOOST_CHECK_EQUAL(func1.eval(), expression_literal(5));
+
+ // More elaborate: ADD(ADD(2, 3), ADD(ADD(4, 5), 6)) ?= 20
+ // f4 f1 f3 f2
+ expression_function f1("ADD", ft);
+ f1.add(E(2));
+ f1.add(E(3));
+ expression_function f2("ADD", ft);
+ f2.add(E(4));
+ f2.add(E(5));
+ expression_function f3("ADD", ft);
+ f3.add(boost::make_shared<expression_function>(f2));
+ f3.add(E(6));
+ expression_function f4("ADD", ft);
+ f4.add(boost::make_shared<expression_function>(f1));
+ f4.add(boost::make_shared<expression_function>(f3));
+
+ BOOST_CHECK_EQUAL(f4.eval().get_int(), 20);
+}
+
+BOOST_AUTO_TEST_CASE(test_function_expression_laziness)
+{
+ function_table::sptr ft = boost::make_shared<functable_mockup_impl>();
+
+ // We run AND(AND(false, false), AND(false, false)).
+ // f1 f2 f3
+ // That makes three ANDs
+ // in total. However, we will only see AND being evaluated twice, because
+ // the outcome is clear after running the first AND in the argument list.
+ expression_function::sptr f2 = boost::make_shared<expression_function>("AND", ft);
+ f2->add(E(false));
+ f2->add(E(false));
+ BOOST_CHECK(not f2->eval().get_bool());
+
+ expression_function::sptr f3 = boost::make_shared<expression_function>("AND", ft);
+ f3->add(E(false));
+ f3->add(E(false));
+ BOOST_CHECK(not f3->eval().get_bool());
+
+ and_counter = 0;
+ expression_function::sptr f1 = boost::make_shared<expression_function>("AND", ft);
+ f1->add(f2);
+ f1->add(f3);
+
+ BOOST_CHECK(not f1->eval().get_bool());
+ BOOST_CHECK_EQUAL(and_counter, 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_sptrs)
+{
+ expression_container::sptr c = expression_container::make();
+ BOOST_CHECK_EQUAL(c->infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK(c->eval().get_bool());
+
+ expression_variable::sptr v = expression_variable::make(
+ "$spp",
+ boost::bind(&variable_get_type, _1), // type-getter
+ boost::bind(&variable_get_value, _1) // value-getter
+ );
+
+ c->add(v);
+ BOOST_REQUIRE_EQUAL(c->infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(c->eval().get_int(), 5);
+}
+
diff --git a/host/tests/nocscript_ftable_test.cpp b/host/tests/nocscript_ftable_test.cpp
new file mode 100644
index 000000000..283245132
--- /dev/null
+++ b/host/tests/nocscript_ftable_test.cpp
@@ -0,0 +1,259 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "../lib/rfnoc/nocscript/function_table.hpp"
+#include <boost/test/unit_test.hpp>
+#include <boost/test/floating_point_comparison.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+#include <boost/make_shared.hpp>
+#include <algorithm>
+#include <iostream>
+
+#include "nocscript_common.hpp"
+
+BOOST_AUTO_TEST_CASE(test_basic_funcs)
+{
+ function_table::sptr ft = function_table::make();
+ BOOST_CHECK(ft->function_exists("ADD"));
+ BOOST_CHECK(ft->function_exists("ADD", two_int_args));
+ BOOST_CHECK(ft->function_exists("LE", two_int_args));
+ BOOST_CHECK(ft->function_exists("GE"));
+ BOOST_CHECK(ft->function_exists("GE", two_int_args));
+ BOOST_CHECK(ft->function_exists("TRUE"));
+ BOOST_CHECK(ft->function_exists("FALSE"));
+
+ // Math
+ expression_container::expr_list_type two_int_values = boost::assign::list_of(E(2))(E(3));
+ expression_container::expr_list_type two_int_values2 = boost::assign::list_of(E(3))(E(2));
+ expression_container::expr_list_type two_double_values = boost::assign::list_of(E(2.0))(E(3.0));
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("ADD", two_int_args), expression::TYPE_INT);
+ expression_literal e_add = ft->eval("ADD", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_add.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_add.get_int(), 5);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("MULT", two_int_args), expression::TYPE_INT);
+ expression_literal e_mult_i = ft->eval("MULT", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_mult_i.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_mult_i.get_int(), 6);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("MULT", two_double_args), expression::TYPE_DOUBLE);
+ expression_literal e_mult_d = ft->eval("MULT", two_double_args, two_double_values);
+ BOOST_REQUIRE_EQUAL(e_mult_d.infer_type(), expression::TYPE_DOUBLE);
+ BOOST_CHECK_CLOSE(e_mult_d.get_double(), 6.0, 0.01);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("DIV", two_double_args), expression::TYPE_DOUBLE);
+ expression_literal e_div_d = ft->eval("DIV", two_double_args, two_double_values);
+ BOOST_REQUIRE_EQUAL(e_div_d.infer_type(), expression::TYPE_DOUBLE);
+ BOOST_CHECK_CLOSE(e_div_d.get_double(), 2.0/3.0, 0.01);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("MODULO", two_int_args), expression::TYPE_INT);
+ expression_literal e_modulo_i = ft->eval("MODULO", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_modulo_i.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_modulo_i.get_int(), 2 % 3);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("LE", two_int_args), expression::TYPE_BOOL);
+ expression_literal e_le = ft->eval("LE", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_le.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(e_le.get_bool(), true);
+ BOOST_CHECK_EQUAL(ft->eval("LE", two_int_args, two_int_values2).get_bool(), false);
+ expression_literal e_ge = ft->eval("GE", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(ft->get_type("GE", two_int_args), expression::TYPE_BOOL);
+ BOOST_REQUIRE_EQUAL(e_ge.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(e_ge.get_bool(), false);
+
+ expression_container::expr_list_type sixty_four = boost::assign::list_of(E(64));
+ expression_literal e_pwr2 = ft->eval("IS_PWR_OF_2", one_int_arg, sixty_four);
+ BOOST_REQUIRE_EQUAL(e_pwr2.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK_EQUAL(e_pwr2.get_bool(), true);
+ expression_literal e_log2 = ft->eval("LOG2", one_int_arg, sixty_four);
+ BOOST_REQUIRE_EQUAL(e_log2.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_log2.get_int(), 6);
+
+ // Boolean Logic
+ expression_container::expr_list_type e_true = boost::assign::list_of(E(true));
+ expression_container::expr_list_type e_false = boost::assign::list_of(E(false));
+ BOOST_CHECK(ft->eval("TRUE", no_args, empty_arg_list).to_bool());
+ BOOST_CHECK(ft->eval("TRUE", no_args, empty_arg_list).get_bool());
+ BOOST_CHECK_EQUAL(ft->eval("FALSE", no_args, empty_arg_list).to_bool(), false);
+ BOOST_CHECK_EQUAL(ft->eval("FALSE", no_args, empty_arg_list).get_bool(), false);
+ BOOST_CHECK(ft->eval("NOT", one_bool_arg, e_false).to_bool());
+
+ // Control
+ std::cout << "Checking ~1s sleep until... ";
+ expression_container::expr_list_type e_sleeptime = boost::assign::list_of(E(.999));
+ BOOST_CHECK(ft->eval("SLEEP", one_double_arg, e_sleeptime).get_bool());
+ std::cout << "Now." << std::endl;
+}
+
+// Some bogus function to test the registry
+expression_literal add_plus2_int(expression_container::expr_list_type args)
+{
+ return expression_literal(args[0]->eval().get_int() + args[1]->eval().get_int() + 2);
+}
+
+BOOST_AUTO_TEST_CASE(test_add_funcs)
+{
+ function_table::sptr ft = function_table::make();
+
+ BOOST_CHECK(not ft->function_exists("ADD_PLUS_2"));
+
+ expression_function::argtype_list_type add_int_args = boost::assign::list_of(expression::TYPE_INT)(expression::TYPE_INT);
+ ft->register_function(
+ "ADD_PLUS_2",
+ boost::bind(&add_plus2_int, _1),
+ expression::TYPE_INT,
+ add_int_args
+ );
+
+ BOOST_CHECK(ft->function_exists("ADD_PLUS_2"));
+ BOOST_CHECK(ft->function_exists("ADD_PLUS_2", add_int_args));
+
+ expression_container::expr_list_type add_int_values = boost::assign::list_of(E(2))(E(3));
+ expression_literal e = ft->eval("ADD_PLUS_2", two_int_args, add_int_values);
+ BOOST_REQUIRE_EQUAL(e.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e.get_int(), 7);
+}
+
+int dummy_true_counter = 0;
+// Some bogus function to test the registry
+expression_literal dummy_true(expression_container::expr_list_type)
+{
+ dummy_true_counter++;
+ std::cout << "Running dummy/true statement." << std::endl;
+ return expression_literal(true);
+}
+
+int dummy_false_counter = 0;
+// Some bogus function to test the registry
+expression_literal dummy_false(expression_container::expr_list_type)
+{
+ dummy_false_counter++;
+ std::cout << "Running dummy/false statement." << std::endl;
+ return expression_literal(false);
+}
+
+BOOST_AUTO_TEST_CASE(test_conditionals)
+{
+ function_table::sptr ft = function_table::make();
+ ft->register_function(
+ "DUMMY",
+ boost::bind(&dummy_true, _1),
+ expression::TYPE_BOOL,
+ no_args
+ );
+ ft->register_function(
+ "DUMMY_F",
+ boost::bind(&dummy_false, _1),
+ expression::TYPE_BOOL,
+ no_args
+ );
+ BOOST_REQUIRE(ft->function_exists("DUMMY", no_args));
+ BOOST_REQUIRE(ft->function_exists("DUMMY_F", no_args));
+
+ expression_function::sptr dummy_statement = boost::make_shared<expression_function>("DUMMY", ft);
+ expression_function::sptr if_statement = boost::make_shared<expression_function>("IF", ft);
+ if_statement->add(E(true));
+ if_statement->add(dummy_statement);
+
+ std::cout << "Dummy statement should run once until END:" << std::endl;
+ dummy_true_counter = 0;
+ BOOST_CHECK(if_statement->eval().get_bool());
+ BOOST_CHECK_EQUAL(dummy_true_counter, 1);
+ std::cout << "END." << std::endl;
+
+ std::cout << "Dummy statement should not run until END:" << std::endl;
+ expression_function::sptr if_statement2 = boost::make_shared<expression_function>("IF", ft);
+ if_statement2->add(E(false));
+ if_statement2->add(dummy_statement);
+ dummy_true_counter = 0;
+ BOOST_CHECK(not if_statement2->eval().get_bool());
+ BOOST_CHECK_EQUAL(dummy_true_counter, 0);
+ std::cout << "END." << std::endl;
+
+ expression_function::sptr if_else_statement = boost::make_shared<expression_function>("IF_ELSE", ft);
+ expression_function::sptr dummy_statement_f = boost::make_shared<expression_function>("DUMMY_F", ft);
+ if_else_statement->add(E(true));
+ if_else_statement->add(dummy_statement);
+ if_else_statement->add(dummy_statement_f);
+ dummy_true_counter = 0;
+ dummy_false_counter = 0;
+ std::cout << "Should execute dummy/true statement before END:" << std::endl;
+ BOOST_CHECK(if_else_statement->eval().get_bool());
+ BOOST_CHECK_EQUAL(dummy_true_counter, 1);
+ BOOST_CHECK_EQUAL(dummy_false_counter, 0);
+ std::cout << "END." << std::endl;
+
+ expression_function::sptr if_else_statement2 = boost::make_shared<expression_function>("IF_ELSE", ft);
+ if_else_statement2->add(E(false));
+ if_else_statement2->add(dummy_statement);
+ if_else_statement2->add(dummy_statement_f);
+ dummy_true_counter = 0;
+ dummy_false_counter = 0;
+ std::cout << "Should execute dummy/false statement before END:" << std::endl;
+ BOOST_CHECK(not if_else_statement2->eval().get_bool());
+ BOOST_CHECK_EQUAL(dummy_true_counter, 0);
+ BOOST_CHECK_EQUAL(dummy_false_counter, 1);
+ std::cout << "END." << std::endl;
+}
+
+BOOST_AUTO_TEST_CASE(test_bitwise_funcs)
+{
+ function_table::sptr ft = function_table::make();
+ BOOST_CHECK(ft->function_exists("SHIFT_RIGHT"));
+ BOOST_CHECK(ft->function_exists("SHIFT_RIGHT", two_int_args));
+ BOOST_CHECK(ft->function_exists("SHIFT_LEFT"));
+ BOOST_CHECK(ft->function_exists("SHIFT_LEFT", two_int_args));
+ BOOST_CHECK(ft->function_exists("BITWISE_AND"));
+ BOOST_CHECK(ft->function_exists("BITWISE_AND", two_int_args));
+ BOOST_CHECK(ft->function_exists("BITWISE_OR"));
+ BOOST_CHECK(ft->function_exists("BITWISE_OR", two_int_args));
+ BOOST_CHECK(ft->function_exists("BITWISE_XOR"));
+ BOOST_CHECK(ft->function_exists("BITWISE_XOR", two_int_args));
+
+ // Bitwise Math
+ int int_value1 = 0x2;
+ int int_value2 = 0x3;
+ expression_container::expr_list_type two_int_values = boost::assign::list_of(E(int_value1))(E(int_value2));
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("SHIFT_RIGHT", two_int_args), expression::TYPE_INT);
+ expression_literal e_shift_right = ft->eval("SHIFT_RIGHT", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_shift_right.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_shift_right.get_int(), int_value1 >> int_value2);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("SHIFT_LEFT", two_int_args), expression::TYPE_INT);
+ expression_literal e_shift_left = ft->eval("SHIFT_LEFT", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_shift_left.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_shift_left.get_int(), int_value1 << int_value2);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_AND", two_int_args), expression::TYPE_INT);
+ expression_literal e_bitwise_and = ft->eval("BITWISE_AND", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_bitwise_and.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_bitwise_and.get_int(), int_value1 & int_value2);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_OR", two_int_args), expression::TYPE_INT);
+ expression_literal e_bitwise_or = ft->eval("BITWISE_OR", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_bitwise_or.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_bitwise_or.get_int(), int_value1 | int_value2);
+
+ BOOST_REQUIRE_EQUAL(ft->get_type("BITWISE_XOR", two_int_args), expression::TYPE_INT);
+ expression_literal e_bitwise_xor = ft->eval("BITWISE_XOR", two_int_args, two_int_values);
+ BOOST_REQUIRE_EQUAL(e_bitwise_xor.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(e_bitwise_xor.get_int(), int_value1 ^ int_value2);
+}
diff --git a/host/tests/nocscript_parser_test.cpp b/host/tests/nocscript_parser_test.cpp
new file mode 100644
index 000000000..a9c25977e
--- /dev/null
+++ b/host/tests/nocscript_parser_test.cpp
@@ -0,0 +1,167 @@
+//
+// Copyright 2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "../lib/rfnoc/nocscript/function_table.hpp"
+#include "../lib/rfnoc/nocscript/parser.hpp"
+#include <uhd/exception.hpp>
+#include <boost/test/unit_test.hpp>
+#include <boost/test/floating_point_comparison.hpp>
+#include <boost/assign/list_of.hpp>
+#include <boost/foreach.hpp>
+#include <boost/bind.hpp>
+#include <boost/make_shared.hpp>
+#include <algorithm>
+#include <iostream>
+
+#include "nocscript_common.hpp"
+
+const int SPP_VALUE = 64;
+
+// Need those for the variable testing:
+expression::type_t variable_get_type(const std::string &var_name)
+{
+ if (var_name == "spp") {
+ std::cout << "Returning type for $spp..." << std::endl;
+ return expression::TYPE_INT;
+ }
+ if (var_name == "is_true") {
+ std::cout << "Returning type for $is_true..." << std::endl;
+ return expression::TYPE_BOOL;
+ }
+
+ throw uhd::syntax_error("Cannot infer type (unknown variable)");
+}
+
+expression_literal variable_get_value(const std::string &var_name)
+{
+ if (var_name == "spp") {
+ std::cout << "Returning value for $spp..." << std::endl;
+ return expression_literal(SPP_VALUE);
+ }
+ if (var_name == "is_true") {
+ std::cout << "Returning value for $is_true..." << std::endl;
+ return expression_literal(true);
+ }
+
+ throw uhd::syntax_error("Cannot read value (unknown variable)");
+}
+
+#define SETUP_FT_AND_PARSER() \
+ function_table::sptr ft = function_table::make(); \
+ parser::sptr p = parser::make( \
+ ft, \
+ boost::bind(&variable_get_type, _1), \
+ boost::bind(&variable_get_value, _1) \
+ );
+
+BOOST_AUTO_TEST_CASE(test_fail)
+{
+ SETUP_FT_AND_PARSER();
+
+ // Missing closing parens:
+ BOOST_REQUIRE_THROW(p->create_expr_tree("ADD1(1, "), uhd::syntax_error);
+ // Double comma:
+ BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1,, 2)"), uhd::syntax_error);
+ // No comma:
+ BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1 2)"), uhd::syntax_error);
+ // Double closing parens:
+ BOOST_REQUIRE_THROW(p->create_expr_tree("ADD(1, 2))"), uhd::syntax_error);
+ // Unknown function:
+ BOOST_REQUIRE_THROW(p->create_expr_tree("GLORPGORP(1, 2)"), uhd::syntax_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_adds_no_vars)
+{
+ SETUP_FT_AND_PARSER();
+ BOOST_REQUIRE(ft->function_exists("ADD"));
+
+ const std::string line("ADD(1, ADD(2, ADD(3, 4)))");
+ expression::sptr e = p->create_expr_tree(line);
+ expression_literal result = e->eval();
+
+ BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(result.get_int(), 1+2+3+4);
+}
+
+BOOST_AUTO_TEST_CASE(test_adds_with_vars)
+{
+ SETUP_FT_AND_PARSER();
+
+ const std::string line("ADD(1, ADD(2, $spp))");
+ expression::sptr e = p->create_expr_tree(line);
+ expression_literal result = e->eval();
+
+ BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_INT);
+ BOOST_CHECK_EQUAL(result.get_int(), 1+2+SPP_VALUE);
+}
+
+BOOST_AUTO_TEST_CASE(test_fft_check)
+{
+ SETUP_FT_AND_PARSER();
+
+ const std::string line("GE($spp, 16) AND LE($spp, 4096) AND IS_PWR_OF_2($spp)");
+ expression::sptr e = p->create_expr_tree(line);
+ expression_literal result = e->eval();
+
+ BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_BOOL);
+ BOOST_CHECK(result.get_bool());
+}
+
+BOOST_AUTO_TEST_CASE(test_pure_string)
+{
+ SETUP_FT_AND_PARSER();
+
+ // Eval all, return last expression
+ const std::string line("'foo foo', \"bar\"");
+ expression_literal result = p->create_expr_tree(line)->eval();
+
+ BOOST_REQUIRE_EQUAL(result.infer_type(), expression::TYPE_STRING);
+ BOOST_CHECK_EQUAL(result.get_string(), "bar");
+}
+
+int dummy_false_counter = 0;
+expression_literal dummy_false(expression_container::expr_list_type)
+{
+ dummy_false_counter++;
+ std::cout << "Running dummy/false statement." << std::endl;
+ return expression_literal(false);
+}
+
+BOOST_AUTO_TEST_CASE(test_multi_commmand)
+{
+ SETUP_FT_AND_PARSER();
+
+ ft->register_function(
+ "DUMMY",
+ boost::bind(&dummy_false, _1),
+ expression::TYPE_BOOL,
+ no_args
+ );
+
+ dummy_false_counter = 0;
+ p->create_expr_tree("DUMMY(), DUMMY(), DUMMY()")->eval();
+ BOOST_CHECK_EQUAL(dummy_false_counter, 3);
+
+ dummy_false_counter = 0;
+ p->create_expr_tree("DUMMY() AND DUMMY() AND DUMMY()")->eval();
+ BOOST_CHECK_EQUAL(dummy_false_counter, 1);
+
+ dummy_false_counter = 0;
+ p->create_expr_tree("DUMMY() OR DUMMY() OR DUMMY()")->eval();
+ BOOST_CHECK_EQUAL(dummy_false_counter, 3);
+}
+
diff --git a/host/tests/node_connect_test.cpp b/host/tests/node_connect_test.cpp
new file mode 100644
index 000000000..ff01828e3
--- /dev/null
+++ b/host/tests/node_connect_test.cpp
@@ -0,0 +1,143 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "graph.hpp"
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::rfnoc;
+
+class source_node : public test_node
+{
+public:
+ typedef boost::shared_ptr<source_node> sptr;
+
+ source_node(const std::string &test_id, size_t output_port)
+ : test_node(test_id)
+ , active_rx_streamer_on_port(0)
+ , _output_port(output_port) {};
+
+ void set_rx_streamer(bool active, const size_t port)
+ {
+ if (active) {
+ std::cout << "[source_node] Someone is registering a rx streamer on port " << port << std::endl;
+ active_rx_streamer_on_port = port;
+ }
+ }
+ size_t active_rx_streamer_on_port;
+
+protected:
+ size_t _request_output_port(
+ const size_t,
+ const uhd::device_addr_t &
+ ) const {
+ return _output_port;
+ }
+
+ const size_t _output_port;
+
+}; /* class result_node */
+
+class sink_node : public test_node
+{
+public:
+ typedef boost::shared_ptr<sink_node> sptr;
+
+ sink_node(const std::string &test_id, size_t input_port)
+ : test_node(test_id)
+ , active_tx_streamer_on_port(0)
+ , _input_port(input_port) {};
+
+ void set_tx_streamer(bool active, const size_t port)
+ {
+ if (active) {
+ std::cout << "[sink_node] Someone is registering a tx streamer on port " << port << std::endl;
+ active_tx_streamer_on_port = port;
+ }
+ }
+ size_t active_tx_streamer_on_port;
+
+protected:
+ size_t _request_input_port(
+ const size_t,
+ const uhd::device_addr_t &
+ ) const {
+ return _input_port;
+ }
+
+ const size_t _input_port;
+
+}; /* class result_node */
+
+#define MAKE_SOURCE_NODE(name, port) source_node::sptr name(new source_node(#name, port));
+#define MAKE_SINK_NODE(name, port) sink_node::sptr name(new sink_node(#name, port));
+
+BOOST_AUTO_TEST_CASE(test_simple_connect)
+{
+ MAKE_SOURCE_NODE(node_A, 42);
+ MAKE_SINK_NODE(node_B, 23);
+
+ size_t src_port = node_A->connect_downstream(node_B, 1);
+ size_t dst_port = node_B->connect_upstream(node_A, 2);
+
+ BOOST_CHECK_EQUAL(src_port, 42);
+ BOOST_CHECK_EQUAL(dst_port, 23);
+
+ node_A->set_downstream_port(src_port, dst_port);
+ node_B->set_upstream_port(dst_port, src_port);
+ BOOST_CHECK_EQUAL(node_A->get_downstream_port(src_port), dst_port);
+ BOOST_CHECK_EQUAL(node_B->get_upstream_port(dst_port), src_port);
+
+ BOOST_REQUIRE_THROW(node_A->get_downstream_port(999), uhd::value_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_fail)
+{
+ MAKE_SOURCE_NODE(node_A, 42);
+ MAKE_SINK_NODE(node_B, ANY_PORT);
+
+ node_A->connect_downstream(node_B, 1);
+ BOOST_REQUIRE_THROW(node_B->connect_upstream(node_A, 2), uhd::type_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_set_streamers)
+{
+ MAKE_SOURCE_NODE(node_A, 0);
+ MAKE_NODE(node_B);
+ MAKE_SINK_NODE(node_C, 0);
+
+ size_t src_port_A = node_A->connect_downstream(node_B, 0);
+ size_t src_port_B = node_B->connect_downstream(node_C, 0);
+ size_t dst_port_B = node_B->connect_upstream(node_A, 0);
+ size_t dst_port_C = node_C->connect_upstream(node_B, 0);
+
+ std::cout << "src_port_A: " << src_port_A << std::endl;
+ std::cout << "src_port_B: " << src_port_B << std::endl;
+ std::cout << "dst_port_B: " << dst_port_B << std::endl;
+ std::cout << "dst_port_C: " << dst_port_C << std::endl;
+
+ node_A->set_downstream_port(src_port_A, dst_port_B);
+ node_B->set_upstream_port(dst_port_B, src_port_A);
+ node_B->set_downstream_port(src_port_B, dst_port_C);
+ node_C->set_upstream_port(dst_port_C, src_port_B);
+
+ node_A->set_tx_streamer(true, 0);
+ node_C->set_rx_streamer(true, 0);
+
+ BOOST_CHECK_EQUAL(node_A->active_rx_streamer_on_port, src_port_A);
+ BOOST_CHECK_EQUAL(node_C->active_tx_streamer_on_port, dst_port_C);
+}
diff --git a/host/tests/property_test.cpp b/host/tests/property_test.cpp
index 00bb3c022..61d1de9a6 100644
--- a/host/tests/property_test.cpp
+++ b/host/tests/property_test.cpp
@@ -28,18 +28,26 @@ struct coercer_type{
};
struct setter_type{
+ setter_type() : _count(0), _x(0) {}
+
void doit(int x){
+ _count++;
_x = x;
}
+ int _count;
int _x;
};
struct getter_type{
+ getter_type() : _count(0), _x(0) {}
+
int doit(void){
+ _count++;
return _x;
}
+ int _count;
int _x;
};
@@ -57,29 +65,72 @@ BOOST_AUTO_TEST_CASE(test_prop_simple){
BOOST_CHECK_EQUAL(prop.get(), 34);
}
-BOOST_AUTO_TEST_CASE(test_prop_with_subscriber){
+BOOST_AUTO_TEST_CASE(test_prop_with_desired_subscriber){
uhd::property_tree::sptr tree = uhd::property_tree::make();
uhd::property<int> &prop = tree->create<int>("/");
setter_type setter;
- prop.subscribe(boost::bind(&setter_type::doit, &setter, _1));
+ prop.add_desired_subscriber(boost::bind(&setter_type::doit, &setter, _1));
prop.set(42);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
BOOST_CHECK_EQUAL(prop.get(), 42);
BOOST_CHECK_EQUAL(setter._x, 42);
prop.set(34);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 34);
BOOST_CHECK_EQUAL(prop.get(), 34);
BOOST_CHECK_EQUAL(setter._x, 34);
}
+BOOST_AUTO_TEST_CASE(test_prop_with_coerced_subscriber){
+ uhd::property_tree::sptr tree = uhd::property_tree::make();
+ uhd::property<int> &prop = tree->create<int>("/");
+
+ setter_type setter;
+ prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1));
+
+ prop.set(42);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
+ BOOST_CHECK_EQUAL(prop.get(), 42);
+ BOOST_CHECK_EQUAL(setter._x, 42);
+
+ prop.set(34);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 34);
+ BOOST_CHECK_EQUAL(prop.get(), 34);
+ BOOST_CHECK_EQUAL(setter._x, 34);
+}
+
+BOOST_AUTO_TEST_CASE(test_prop_manual_coercion){
+ uhd::property_tree::sptr tree = uhd::property_tree::make();
+ uhd::property<int> &prop = tree->create<int>("/", uhd::property_tree::MANUAL_COERCE);
+
+ setter_type dsetter, csetter;
+ prop.add_desired_subscriber(boost::bind(&setter_type::doit, &dsetter, _1));
+ prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &csetter, _1));
+
+ BOOST_CHECK_EQUAL(dsetter._x, 0);
+ BOOST_CHECK_EQUAL(csetter._x, 0);
+
+ prop.set(42);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
+ BOOST_CHECK_EQUAL(dsetter._x, 42);
+ BOOST_CHECK_EQUAL(csetter._x, 0);
+
+ prop.set_coerced(34);
+ BOOST_CHECK_EQUAL(prop.get_desired(), 42);
+ BOOST_CHECK_EQUAL(prop.get(), 34);
+ BOOST_CHECK_EQUAL(dsetter._x, 42);
+ BOOST_CHECK_EQUAL(csetter._x, 34);
+}
+
BOOST_AUTO_TEST_CASE(test_prop_with_publisher){
uhd::property_tree::sptr tree = uhd::property_tree::make();
uhd::property<int> &prop = tree->create<int>("/");
BOOST_CHECK(prop.empty());
getter_type getter;
- prop.publish(boost::bind(&getter_type::doit, &getter));
+ prop.set_publisher(boost::bind(&getter_type::doit, &getter));
BOOST_CHECK(not prop.empty());
getter._x = 42;
@@ -96,10 +147,10 @@ BOOST_AUTO_TEST_CASE(test_prop_with_publisher_and_subscriber){
uhd::property<int> &prop = tree->create<int>("/");
getter_type getter;
- prop.publish(boost::bind(&getter_type::doit, &getter));
+ prop.set_publisher(boost::bind(&getter_type::doit, &getter));
setter_type setter;
- prop.subscribe(boost::bind(&setter_type::doit, &setter, _1));
+ prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1));
getter._x = 42;
prop.set(0);
@@ -117,10 +168,10 @@ BOOST_AUTO_TEST_CASE(test_prop_with_coercion){
uhd::property<int> &prop = tree->create<int>("/");
setter_type setter;
- prop.subscribe(boost::bind(&setter_type::doit, &setter, _1));
+ prop.add_coerced_subscriber(boost::bind(&setter_type::doit, &setter, _1));
coercer_type coercer;
- prop.coerce(boost::bind(&coercer_type::doit, &coercer, _1));
+ prop.set_coercer(boost::bind(&coercer_type::doit, &coercer, _1));
prop.set(42);
BOOST_CHECK_EQUAL(prop.get(), 40);
diff --git a/host/tests/rate_node_test.cpp b/host/tests/rate_node_test.cpp
new file mode 100644
index 000000000..2e17c02a5
--- /dev/null
+++ b/host/tests/rate_node_test.cpp
@@ -0,0 +1,139 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "graph.hpp"
+#include <uhd/rfnoc/rate_node_ctrl.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::rfnoc;
+
+// test class derived, knows about rates
+class rate_aware_node : public test_node, public rate_node_ctrl
+{
+public:
+ typedef boost::shared_ptr<rate_aware_node> sptr;
+
+ rate_aware_node(const std::string &test_id) : test_node(test_id) {};
+
+}; /* class rate_aware_node */
+
+// test class derived, sets rates
+class rate_setting_node : public test_node, public rate_node_ctrl
+{
+public:
+ typedef boost::shared_ptr<rate_setting_node> sptr;
+
+ rate_setting_node(const std::string &test_id, double samp_rate) : test_node(test_id), _samp_rate(samp_rate) {};
+
+ double get_input_samp_rate(size_t) { return _samp_rate; };
+ double get_output_samp_rate(size_t) { return _samp_rate; };
+
+private:
+ double _samp_rate;
+
+}; /* class rate_setting_node */
+
+#define MAKE_RATE_NODE(name) rate_aware_node::sptr name(new rate_aware_node(#name));
+#define MAKE_RATE_SETTING_NODE(name, rate) rate_setting_node::sptr name(new rate_setting_node(#name, rate));
+
+BOOST_AUTO_TEST_CASE(test_simplest_downstream_search)
+{
+ const double test_rate = 0.25;
+ MAKE_RATE_NODE(node_A);
+ MAKE_RATE_SETTING_NODE(node_B, test_rate);
+
+ // Simplest possible scenario: Connect B downstream of A and let
+ // it find B
+ connect_nodes(node_A, node_B);
+
+ double result_rate = node_A->get_input_samp_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
+BOOST_AUTO_TEST_CASE(test_skip_downstream_search)
+{
+ const double test_rate = 0.25;
+ MAKE_RATE_NODE(node_A);
+ MAKE_NODE(node_B);
+ MAKE_RATE_SETTING_NODE(node_C, test_rate);
+
+ // Slightly more elaborate: Add another block in between that has no
+ // clue about rates
+ connect_nodes(node_A, node_B);
+ connect_nodes(node_B, node_C);
+
+ double result_rate = node_A->get_input_samp_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
+BOOST_AUTO_TEST_CASE(test_tree_downstream_search)
+{
+ const double test_rate = 0.25;
+ MAKE_RATE_NODE(node_A);
+ MAKE_NODE(node_B0);
+ MAKE_RATE_SETTING_NODE(node_B1, test_rate);
+ MAKE_RATE_SETTING_NODE(node_C0, test_rate);
+ MAKE_RATE_SETTING_NODE(node_C1, rate_node_ctrl::RATE_UNDEFINED);
+
+ // Tree: Downstream of our first node are 3 rate setting blocks.
+ // Two set the same rate, the third does not care.
+ connect_nodes(node_A, node_B0);
+ connect_nodes(node_A, node_B1);
+ connect_nodes(node_B0, node_C0);
+ connect_nodes(node_B0, node_C1);
+
+ double result_rate = node_A->get_input_samp_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
+BOOST_AUTO_TEST_CASE(test_tree_downstream_search_throw)
+{
+ const double test_rate = 0.25;
+ MAKE_RATE_NODE(node_A);
+ MAKE_NODE(node_B0);
+ MAKE_RATE_SETTING_NODE(node_B1, test_rate);
+ MAKE_RATE_SETTING_NODE(node_C0, test_rate);
+ MAKE_RATE_SETTING_NODE(node_C1, test_rate * 2);
+
+ // Tree: Downstream of our first node are 3 rate setting blocks.
+ // Two set the same rate, the third has a different rate.
+ // This will cause a throw.
+ connect_nodes(node_A, node_B0);
+ connect_nodes(node_A, node_B1);
+ connect_nodes(node_B0, node_C0);
+ connect_nodes(node_B0, node_C1);
+
+ BOOST_CHECK_THROW(node_A->get_input_samp_rate(), uhd::runtime_error);
+}
+
+BOOST_AUTO_TEST_CASE(test_skip_upstream_search)
+{
+ const double test_rate = 0.25;
+ MAKE_RATE_SETTING_NODE(node_A, test_rate);
+ MAKE_NODE(node_B);
+ MAKE_RATE_NODE(node_C);
+
+ // Slightly more elaborate: Add another block in between that has no
+ // clue about rates
+ connect_nodes(node_A, node_B);
+ connect_nodes(node_B, node_C);
+
+ double result_rate = node_C->get_output_samp_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
diff --git a/host/tests/stream_sig_test.cpp b/host/tests/stream_sig_test.cpp
new file mode 100644
index 000000000..904a14053
--- /dev/null
+++ b/host/tests/stream_sig_test.cpp
@@ -0,0 +1,82 @@
+//
+// Copyright 2014-2015 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <iostream>
+#include <boost/test/unit_test.hpp>
+#include <uhd/exception.hpp>
+#include <uhd/rfnoc/stream_sig.hpp>
+
+using namespace uhd::rfnoc;
+
+BOOST_AUTO_TEST_CASE(test_stream_sig) {
+ stream_sig_t stream_sig;
+
+ BOOST_CHECK_EQUAL(stream_sig.item_type, "");
+ BOOST_CHECK_EQUAL(stream_sig.vlen, 0);
+ BOOST_CHECK_EQUAL(stream_sig.packet_size, 0);
+ BOOST_CHECK_EQUAL(stream_sig.is_bursty, false);
+
+ std::stringstream ss;
+ ss << stream_sig;
+ // Eventually actually test the contents
+ std::cout << ss.str() << std::endl;
+}
+
+BOOST_AUTO_TEST_CASE(test_stream_sig_compat) {
+ stream_sig_t upstream_sig;
+ stream_sig_t downstream_sig;
+
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ upstream_sig.vlen = 32;
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ downstream_sig.vlen = 32;
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ upstream_sig.vlen = 16;
+ BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ upstream_sig.vlen = 32;
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ upstream_sig.packet_size = 8;
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ downstream_sig.packet_size = 12;
+ BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ upstream_sig.packet_size = 0;
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ downstream_sig.item_type = "";
+ BOOST_CHECK(stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+ upstream_sig.item_type = "sc16";
+ downstream_sig.item_type = "s8";
+ BOOST_CHECK(not stream_sig_t::is_compatible(upstream_sig, downstream_sig));
+}
+
+BOOST_AUTO_TEST_CASE(test_stream_sig_types) {
+ stream_sig_t stream_sig;
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 0);
+ stream_sig.item_type = "sc16";
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 4);
+ stream_sig.item_type = "sc12";
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 3);
+ stream_sig.item_type = "sc8";
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 2);
+ stream_sig.item_type = "s16";
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 2);
+ stream_sig.item_type = "s8";
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 1);
+ stream_sig.item_type = "fc32";
+ BOOST_CHECK_EQUAL(stream_sig.get_bytes_per_item(), 8);
+ stream_sig.item_type = "not_a_type";
+ BOOST_REQUIRE_THROW(stream_sig.get_bytes_per_item(), uhd::key_error);
+}
diff --git a/host/tests/tick_node_test.cpp b/host/tests/tick_node_test.cpp
new file mode 100644
index 000000000..fc1b8c544
--- /dev/null
+++ b/host/tests/tick_node_test.cpp
@@ -0,0 +1,110 @@
+//
+// Copyright 2014 Ettus Research LLC
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include "graph.hpp"
+#include <uhd/rfnoc/tick_node_ctrl.hpp>
+#include <boost/test/unit_test.hpp>
+#include <iostream>
+
+using namespace uhd::rfnoc;
+
+// test class derived, knows about rates
+class tick_aware_node : public test_node, public tick_node_ctrl
+{
+public:
+ typedef boost::shared_ptr<tick_aware_node> sptr;
+
+ tick_aware_node(const std::string &test_id) : test_node(test_id) {};
+
+}; /* class tick_aware_node */
+
+// test class derived, sets rates
+class tick_setting_node : public test_node, public tick_node_ctrl
+{
+public:
+ typedef boost::shared_ptr<tick_setting_node> sptr;
+
+ tick_setting_node(const std::string &test_id, double tick_rate) : test_node(test_id), _tick_rate(tick_rate) {};
+
+protected:
+ double _get_tick_rate() { return _tick_rate; };
+
+private:
+ const double _tick_rate;
+
+}; /* class tick_setting_node */
+
+#define MAKE_TICK_NODE(name) tick_aware_node::sptr name(new tick_aware_node(#name));
+#define MAKE_TICK_SETTING_NODE(name, rate) tick_setting_node::sptr name(new tick_setting_node(#name, rate));
+
+BOOST_AUTO_TEST_CASE(test_simplest_downstream_search)
+{
+ const double test_rate = 0.25;
+ MAKE_TICK_NODE(node_A);
+ MAKE_TICK_SETTING_NODE(node_B, test_rate);
+
+ // Simplest possible scenario: Connect B downstream of A and let
+ // it find B
+ connect_nodes(node_A, node_B);
+
+ double result_rate = node_A->get_tick_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
+BOOST_AUTO_TEST_CASE(test_both_ways_search)
+{
+ const double test_rate = 0.25;
+ MAKE_TICK_SETTING_NODE(node_A, tick_node_ctrl::RATE_UNDEFINED);
+ MAKE_TICK_NODE(node_B);
+ MAKE_TICK_SETTING_NODE(node_C, test_rate);
+
+ std::cout << "a->b" << std::endl;
+ connect_nodes(node_A, node_B);
+ std::cout << "b->a" << std::endl;
+ connect_nodes(node_B, node_C);
+
+ std::cout << "search" << std::endl;
+ double result_rate = node_B->get_tick_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
+BOOST_AUTO_TEST_CASE(test_both_ways_search_reversed)
+{
+ const double test_rate = 0.25;
+ MAKE_TICK_SETTING_NODE(node_A, test_rate);
+ MAKE_TICK_NODE(node_B);
+ MAKE_TICK_SETTING_NODE(node_C, tick_node_ctrl::RATE_UNDEFINED);
+
+ connect_nodes(node_A, node_B);
+ connect_nodes(node_B, node_C);
+
+ double result_rate = node_B->get_tick_rate();
+ BOOST_CHECK_EQUAL(result_rate, test_rate);
+}
+
+BOOST_AUTO_TEST_CASE(test_both_ways_search_fail)
+{
+ const double test_rate = 0.25;
+ MAKE_TICK_SETTING_NODE(node_A, test_rate);
+ MAKE_TICK_NODE(node_B);
+ MAKE_TICK_SETTING_NODE(node_C, 2 * test_rate);
+
+ connect_nodes(node_A, node_B);
+ connect_nodes(node_B, node_C);
+
+ BOOST_CHECK_THROW(node_B->get_tick_rate(), uhd::runtime_error);
+}