diff options
Diffstat (limited to 'host/tests')
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); +}  | 
