diff options
author | Martin Braun <martin.braun@ettus.com> | 2016-08-01 18:17:41 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2016-08-09 12:42:52 -0700 |
commit | 3bf4b000f7d9a7f4af82c21753556ede7e8df6e3 (patch) | |
tree | 2228d7eb58c4d83d91192cb9b6a908e4e49f6317 /host/tests | |
parent | c5b076173e2d866f3ee99c113a37183c5ec20f0b (diff) | |
download | uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.tar.gz uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.tar.bz2 uhd-3bf4b000f7d9a7f4af82c21753556ede7e8df6e3.zip |
Merging RFNoC support for X310
Diffstat (limited to 'host/tests')
-rw-r--r-- | host/tests/CMakeLists.txt | 43 | ||||
-rw-r--r-- | host/tests/block_id_test.cpp | 117 | ||||
-rw-r--r-- | host/tests/blockdef_test.cpp | 94 | ||||
-rw-r--r-- | host/tests/device3_test.cpp | 175 | ||||
-rw-r--r-- | host/tests/graph.hpp | 51 | ||||
-rw-r--r-- | host/tests/graph_search_test.cpp | 169 | ||||
-rw-r--r-- | host/tests/nocscript_common.hpp | 35 | ||||
-rw-r--r-- | host/tests/nocscript_expr_test.cpp | 451 | ||||
-rw-r--r-- | host/tests/nocscript_ftable_test.cpp | 259 | ||||
-rw-r--r-- | host/tests/nocscript_parser_test.cpp | 167 | ||||
-rw-r--r-- | host/tests/node_connect_test.cpp | 143 | ||||
-rw-r--r-- | host/tests/rate_node_test.cpp | 139 | ||||
-rw-r--r-- | host/tests/stream_sig_test.cpp | 82 | ||||
-rw-r--r-- | host/tests/tick_node_test.cpp | 110 |
14 files changed, 2035 insertions, 0 deletions
diff --git a/host/tests/CMakeLists.txt b/host/tests/CMakeLists.txt index c5f25913e..8f7fdcd7c 100644 --- a/host/tests/CMakeLists.txt +++ b/host/tests/CMakeLists.txt @@ -52,6 +52,19 @@ SET(test_sources #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 @@ -72,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 ######################################################################## 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/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/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..2ee5dafdf --- /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 &name, + const function_table::function_ptr &ptr, + const expression::type_t return_type, + const expression_function::argtype_list_type &sig + ) {}; + +}; + + +// 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/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); +} |