diff --git a/doc/release/yarp_3_9/yarprobotinterface_extern_blob.md b/doc/release/yarp_3_9/yarprobotinterface_extern_blob.md new file mode 100644 index 00000000000..ab0ee155837 --- /dev/null +++ b/doc/release/yarp_3_9/yarprobotinterface_extern_blob.md @@ -0,0 +1,3 @@ +#### `libYARP_robotinterface` + +* Add support for blob values in the extra configuration passed to `XMLReader::getRobotFrom{File|String}` and linked in the XML via `extern-name`. diff --git a/src/libYARP_robotinterface/src/yarp/robotinterface/impl/XMLReaderFileV3.cpp b/src/libYARP_robotinterface/src/yarp/robotinterface/impl/XMLReaderFileV3.cpp index 3e4269bb7d7..4c632db016f 100644 --- a/src/libYARP_robotinterface/src/yarp/robotinterface/impl/XMLReaderFileV3.cpp +++ b/src/libYARP_robotinterface/src/yarp/robotinterface/impl/XMLReaderFileV3.cpp @@ -573,7 +573,11 @@ yarp::robotinterface::Param yarp::robotinterface::impl::XMLReaderFileV3::Private // FIXME Check DTD >= 3.1 if (config.find(extern_name).isList()) { - param.value() = "(" + config.find(extern_name).asList()->toString() + ")"; + param.value() = "(" + config.find(extern_name).toString() + ")"; + } + else if (config.find(extern_name).isBlob()) + { + param.value() = "{" + config.find(extern_name).toString() + "}"; } else { diff --git a/src/libYARP_robotinterface/tests/RobotinterfaceTest.cpp b/src/libYARP_robotinterface/tests/RobotinterfaceTest.cpp index d86ee9ab892..f33a26af20c 100644 --- a/src/libYARP_robotinterface/tests/RobotinterfaceTest.cpp +++ b/src/libYARP_robotinterface/tests/RobotinterfaceTest.cpp @@ -45,7 +45,9 @@ struct GlobalState bool mockDetachWasCalled; bool mockWrapperWasClosed; bool mockDriverWasClosed; - std::string mockDriverParamValue; + std::string mockDriverParamStringValue; + yarp::os::Bottle mockDriverParamListValue; + const void * mockDriverParamBlobValue; void reset() { @@ -55,7 +57,9 @@ struct GlobalState mockDetachWasCalled = false; mockWrapperWasClosed = false; mockDriverWasClosed = false; - mockDriverParamValue.clear(); + mockDriverParamStringValue.clear(); + mockDriverParamListValue.clear(); + mockDriverParamBlobValue = nullptr; } }; @@ -78,8 +82,19 @@ yarp::dev::RobotInterfaceTestMockDriver::~RobotInterfaceTestMockDriver() bool yarp::dev::RobotInterfaceTestMockDriver::open(yarp::os::Searchable& config) { + static const yarp::os::Bottle emptyList("()"); globalState.mockDriverWasOpened = true; - globalState.mockDriverParamValue = config.check("theparam", yarp::os::Value("theparam_unset"), "The param").asString(); + globalState.mockDriverParamStringValue = config.check("theparam", yarp::os::Value("theparam_unset"), "The string param").asString(); + globalState.mockDriverParamListValue = *config.check("thelistparam", emptyList.get(0), "The list param").asList(); + + // don't use the `check` signature as it creates a temporary + if (const auto* ptr = config.find("theblobparam").asBlob(); ptr != nullptr) + { + // the caller test suite outlives `config`, thus we passed a pointer to the data instead of the data itself, + // and we need to dereference it here + globalState.mockDriverParamBlobValue = *reinterpret_cast(ptr); + } + return true; } @@ -150,7 +165,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") SECTION("Check valid robot file with no devices") { - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -176,7 +191,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Add dummy devices to YARP drivers factory yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -198,7 +213,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Verify that the devices were not opened and the attach was not called CHECK(!globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue.empty()); + CHECK(globalState.mockDriverParamStringValue.empty()); // Start the robot (open the device and call "attach" actions) bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); @@ -207,7 +222,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was opened and attach called CHECK(globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "theparam_unset"); + CHECK(globalState.mockDriverParamStringValue == "theparam_unset"); // Stop the robot ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); @@ -218,7 +233,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was closed and detach called CHECK(globalState.mockDriverWasOpened); CHECK(globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "theparam_unset"); + CHECK(globalState.mockDriverParamStringValue == "theparam_unset"); } SECTION("Check valid robot file with fixed param") @@ -229,7 +244,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Add dummy devices to YARP drivers factory yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -252,7 +267,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Verify that the devices were not opened and the attach was not called CHECK(!globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue.empty()); + CHECK(globalState.mockDriverParamStringValue.empty()); // Start the robot (open the device and call "attach" actions) bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); @@ -261,7 +276,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was opened and attach called CHECK(globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "theparam_fixed"); + CHECK(globalState.mockDriverParamStringValue == "theparam_fixed"); // Stop the robot ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); @@ -272,10 +287,10 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was closed and detach called CHECK(globalState.mockDriverWasOpened); CHECK(globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "theparam_fixed"); + CHECK(globalState.mockDriverParamStringValue == "theparam_fixed"); } - SECTION("Check valid robot file with extern param") + SECTION("Check valid robot file with extern string param") { // Reset test flags globalState.reset(); @@ -283,7 +298,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Add dummy devices to YARP drivers factory yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -308,7 +323,63 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Verify that the devices were not opened and the attach was not called CHECK(!globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue.empty()); + CHECK(globalState.mockDriverParamStringValue.empty()); + + // Start the robot (open the device and call "attach" actions) + bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); + CHECK(ok); + + // Check that the device was opened and attach called + CHECK(globalState.mockDriverWasOpened); + CHECK(!globalState.mockDriverWasClosed); + CHECK(globalState.mockDriverParamStringValue == "theparam_alt"); + + // Stop the robot + ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); + CHECK(ok); + ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseShutdown); + CHECK(ok); + + // Check that the device was closed and detach called + CHECK(globalState.mockDriverWasOpened); + CHECK(globalState.mockDriverWasClosed); + CHECK(globalState.mockDriverParamStringValue == "theparam_alt"); + } + + SECTION("Check valid robot file with extern list param") + { + // Reset test flags + globalState.reset(); + + // Add dummy devices to YARP drivers factory + yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); + + // Load XML configuration file + std::string XMLString = "\n" + "\n" + "\n" + " \n" + " \n" + " () \n" + " \n" + " \n" + "\n"; + + yarp::robotinterface::XMLReader reader; + yarp::os::Property config; + config.put("thelistparam", yarp::os::Value::makeList("theparam_alt")); + yarp::robotinterface::XMLReaderResult result = reader.getRobotFromString(XMLString, config); + + // Check parsing fails on empty string + CHECK(result.parsingIsSuccessful); + + // Verify that only one device has been loaded + CHECK(result.robot.devices().size() == 1); + + // Verify that the devices were not opened and the attach was not called + CHECK(!globalState.mockDriverWasOpened); + CHECK(!globalState.mockDriverWasClosed); + CHECK(globalState.mockDriverParamListValue.size() == 0); // Start the robot (open the device and call "attach" actions) bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); @@ -317,7 +388,8 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was opened and attach called CHECK(globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "theparam_alt"); + CHECK(globalState.mockDriverParamListValue.size() == 1); + CHECK(globalState.mockDriverParamListValue.get(0).asString() == "theparam_alt"); // Stop the robot ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); @@ -328,7 +400,66 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was closed and detach called CHECK(globalState.mockDriverWasOpened); CHECK(globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "theparam_alt"); + CHECK(globalState.mockDriverParamListValue.size() == 1); + CHECK(globalState.mockDriverParamListValue.get(0).asString() == "theparam_alt"); + } + + SECTION("Check valid robot file with extern blob param") + { + // Reset test flags + globalState.reset(); + + // Add dummy devices to YARP drivers factory + yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); + + // Load XML configuration file + std::string XMLString = "\n" + "\n" + "\n" + " \n" + " \n" + " 0 \n" + " \n" + " \n" + "\n"; + + auto* blobValue = &XMLString; + + yarp::robotinterface::XMLReader reader; + yarp::os::Property config; + config.put("theblobparam", yarp::os::Value::makeBlob(&blobValue, sizeof(blobValue))); + yarp::robotinterface::XMLReaderResult result = reader.getRobotFromString(XMLString, config); + + // Check parsing fails on empty string + CHECK(result.parsingIsSuccessful); + + // Verify that only one device has been loaded + CHECK(result.robot.devices().size() == 1); + + // Verify that the devices were not opened and the attach was not called + CHECK(!globalState.mockDriverWasOpened); + CHECK(!globalState.mockDriverWasClosed); + CHECK(globalState.mockDriverParamBlobValue == nullptr); + + // Start the robot (open the device and call "attach" actions) + bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); + CHECK(ok); + + // Check that the device was opened and attach called + CHECK(globalState.mockDriverWasOpened); + CHECK(!globalState.mockDriverWasClosed); + CHECK(globalState.mockDriverParamBlobValue == blobValue); + + // Stop the robot + ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); + CHECK(ok); + ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseShutdown); + CHECK(ok); + + // Check that the device was closed and detach called + CHECK(globalState.mockDriverWasOpened); + CHECK(globalState.mockDriverWasClosed); + CHECK(globalState.mockDriverParamBlobValue == blobValue); } SECTION("Check valid robot file with portprefix passed via xml") @@ -339,7 +470,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Add dummy devices to YARP drivers factory yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -363,7 +494,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Verify that the devices were not opened and the attach was not called CHECK(!globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue.empty()); + CHECK(globalState.mockDriverParamStringValue.empty()); // Start the robot (open the device and call "attach" actions) bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); @@ -372,7 +503,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was opened and attach called CHECK(globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "/RobotWithOneDevice/SomethingInTheMiddle/RobotWithOneDevice"); + CHECK(globalState.mockDriverParamStringValue == "/RobotWithOneDevice/SomethingInTheMiddle/RobotWithOneDevice"); // Stop the robot ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); @@ -383,7 +514,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was closed and detach called CHECK(globalState.mockDriverWasOpened); CHECK(globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "/RobotWithOneDevice/SomethingInTheMiddle/RobotWithOneDevice"); + CHECK(globalState.mockDriverParamStringValue == "/RobotWithOneDevice/SomethingInTheMiddle/RobotWithOneDevice"); } SECTION("Check valid robot file with portprefix overriden via configuration") @@ -394,7 +525,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Add dummy devices to YARP drivers factory yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -419,7 +550,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Verify that the devices were not opened and the attach was not called CHECK(!globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue.empty()); + CHECK(globalState.mockDriverParamStringValue.empty()); // Start the robot (open the device and call "attach" actions) bool ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseStartup); @@ -428,7 +559,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was opened and attach called CHECK(globalState.mockDriverWasOpened); CHECK(!globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "/RobotWithOneDeviceAlternativePrefix/SomethingInTheMiddle/RobotWithOneDeviceAlternativePrefix"); + CHECK(globalState.mockDriverParamStringValue == "/RobotWithOneDeviceAlternativePrefix/SomethingInTheMiddle/RobotWithOneDeviceAlternativePrefix"); // Stop the robot ok = result.robot.enterPhase(yarp::robotinterface::ActionPhaseInterrupt1); @@ -439,7 +570,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") // Check that the device was closed and detach called CHECK(globalState.mockDriverWasOpened); CHECK(globalState.mockDriverWasClosed); - CHECK(globalState.mockDriverParamValue == "/RobotWithOneDeviceAlternativePrefix/SomethingInTheMiddle/RobotWithOneDeviceAlternativePrefix"); + CHECK(globalState.mockDriverParamStringValue == "/RobotWithOneDeviceAlternativePrefix/SomethingInTheMiddle/RobotWithOneDeviceAlternativePrefix"); } SECTION("Check valid robot file with two devices") @@ -451,7 +582,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_device", "", "RobotInterfaceTestMockDriver")); yarp::dev::Drivers::factory().add(new yarp::dev::DriverCreatorOf("robotinterface_test_mock_wrapper", "", "RobotInterfaceTestMockWrapper")); - // Load empty XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -533,7 +664,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") yarp::dev::PolyDriverList externalDriverList; externalDriverList.push(&dummyDevice, "dummy_device"); - // Load XML configuration file + // Load XML configuration file std::string XMLString = "\n" "\n" "\n" @@ -620,7 +751,7 @@ TEST_CASE("robotinterface::XMLReaderTest", "[yarp::robotinterface]") yarp::dev::PolyDriverList externalDriverList; externalDriverList.push(&dummyDevice, "dummy_device"); - // Load XML configuration file that also contains an internal device with name "dummy_device" + // Load XML configuration file that also contains an internal device with name "dummy_device" std::string XMLString = "\n" "\n" "\n"