Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion resources/external_control.urscript
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,8 @@ while control_mode > MODE_STOPPED:
end
else:
textmsg("Socket timed out waiting for command on reverse_socket. The script will exit now.")
control_mode = MODE_STOPPED
stopj(STOPJ_ACCELERATION)
halt
end
exit_critical
end
Expand Down
16 changes: 16 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ if (INTEGRATION_TESTS)
TEST_SUFFIX _headless
)

# ExternalControlProgram tests
add_executable(external_control_program_tests_urcap test_external_control_program.cpp)
target_link_libraries(external_control_program_tests_urcap PRIVATE ur_client_library::urcl GTest::gtest_main)
gtest_add_tests(TARGET external_control_program_tests_urcap
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
EXTRA_ARGS --headless false
TEST_SUFFIX _urcap
)
add_executable(external_control_program_tests_headless test_external_control_program.cpp)
target_link_libraries(external_control_program_tests_headless PRIVATE ur_client_library::urcl GTest::gtest_main)
gtest_add_tests(TARGET external_control_program_tests_headless
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
EXTRA_ARGS --headless true
TEST_SUFFIX _headless
)

# InstructionExecutor tests
add_executable(instruction_executor_test_urcap test_instruction_executor.cpp)
target_link_libraries(instruction_executor_test_urcap PRIVATE ur_client_library::urcl GTest::gtest_main)
Expand Down
140 changes: 140 additions & 0 deletions tests/test_external_control_program.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// -- BEGIN LICENSE BLOCK ----------------------------------------------
// Copyright 2026 Universal Robots A/S
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the {copyright_holder} nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// -- END LICENSE BLOCK ------------------------------------------------

#include <gtest/gtest.h>

#include "test_utils.h"
#include "ur_client_library/example_robot_wrapper.h"

using namespace urcl;
const std::string SCRIPT_FILE = "../resources/external_control.urscript";
const std::string OUTPUT_RECIPE = "resources/rtde_output_recipe.txt";
const std::string INPUT_RECIPE = "resources/rtde_input_recipe.txt";
std::string g_ROBOT_IP = "192.168.56.101";
bool g_HEADLESS = true;

std::unique_ptr<ExampleRobotWrapper> g_my_robot;

class ExternalControlProgramTest : public ::testing::Test
{
protected:
static void SetUpTestSuite()
{
if (!(robotVersionLessThan(g_ROBOT_IP, "10.0.0") || g_HEADLESS))
{
GTEST_SKIP_("Running URCap tests for PolyScope X is currently not supported.");
}
}
void SetUp() override
{
std::string modified_script_path = extendScript(SCRIPT_FILE);

g_my_robot = std::make_unique<ExampleRobotWrapper>(g_ROBOT_IP, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS,
"external_control.urp", modified_script_path);
if (!g_my_robot->isHealthy())
{
ASSERT_TRUE(g_my_robot->resendRobotProgram());
ASSERT_TRUE(g_my_robot->waitForProgramRunning(500));
}
server_.reset(new TestableTcpServer(60005));
server_->start();
}

void TearDown() override
{
server_.reset();
}

std::string extendScript(const std::string& script_path)
{
char modified_script_path[] = "urscript.XXXXXX";
#ifdef _WIN32
# define mkstemp _mktemp_s
#endif
std::ignore = mkstemp(modified_script_path);

std::ofstream ofs(modified_script_path);
if (ofs.bad())
{
std::cout << "Failed to create temporary files" << std::endl;
throw std::runtime_error("Failed to create temporary files");
}
std::ifstream in_file(script_path);
std::string prog((std::istreambuf_iterator<char>(in_file)), (std::istreambuf_iterator<char>()));
prog += "\nsocket_open(\"{{SERVER_IP_REPLACE}}\", 60005, \"test_socket\")\n";
prog += "\nsleep(0.6)\n";
prog += "\ntextmsg(\"sleeping done.\")\n";
ofs << prog;
ofs.close();

return modified_script_path;
}

std::unique_ptr<TestableTcpServer> server_;
};

TEST_F(ExternalControlProgramTest, program_halts_on_timeout)
{
vector6d_t zeros = { 0, 0, 0, 0, 0, 0 };
g_my_robot->getUrDriver()->writeJointCommand(zeros, comm::ControlMode::MODE_IDLE, RobotReceiveTimeout::millisec(200));
EXPECT_FALSE(server_->waitForConnectionCallback(1000));
}

TEST_F(ExternalControlProgramTest, stop_control_does_not_halt_program)
{
vector6d_t zeros = { 0, 0, 0, 0, 0, 0 };
g_my_robot->getUrDriver()->writeJointCommand(zeros, comm::ControlMode::MODE_IDLE, RobotReceiveTimeout::off());

// Make sure that we can stop the robot control, when robot receive timeout has been set off
g_my_robot->getUrDriver()->stopControl();
EXPECT_TRUE(server_->waitForConnectionCallback(1000));
}

int main(int argc, char* argv[])
{
::testing::InitGoogleTest(&argc, argv);

for (int i = 0; i < argc; i++)
{
if (std::string(argv[i]) == "--robot_ip" && i + 1 < argc)
{
g_ROBOT_IP = argv[i + 1];
++i;
}
if (std::string(argv[i]) == "--headless" && i + 1 < argc)
{
std::string headless = argv[i + 1];
g_HEADLESS = headless == "true" || headless == "1" || headless == "True" || headless == "TRUE";
++i;
}
}
Comment thread
cursor[bot] marked this conversation as resolved.

return RUN_ALL_TESTS();
}
44 changes: 11 additions & 33 deletions tests/test_pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include <gtest/gtest.h>
#include <condition_variable>

#include "test_utils.h"

#include <ur_client_library/comm/pipeline.h>
#include <ur_client_library/comm/tcp_server.h>
#include <ur_client_library/comm/stream.h>
Expand All @@ -45,8 +47,7 @@ class PipelineTest : public ::testing::Test
protected:
void SetUp()
{
server_.reset(new comm::TCPServer(60002));
server_->setConnectCallback(std::bind(&PipelineTest::connectionCallback, this, std::placeholders::_1));
server_.reset(new TestableTcpServer(60002));
server_->start();

// Setup pipeline
Expand All @@ -68,28 +69,7 @@ class PipelineTest : public ::testing::Test
server_.reset();
}

void connectionCallback(const socket_t filedescriptor)
{
std::lock_guard<std::mutex> lk(connect_mutex_);
client_fd_ = filedescriptor;
connect_cv_.notify_one();
connection_callback_ = true;
}

bool waitForConnectionCallback(int milliseconds = 100)
{
std::unique_lock<std::mutex> lk(connect_mutex_);
if (connect_cv_.wait_for(lk, std::chrono::milliseconds(milliseconds)) == std::cv_status::no_timeout ||
connection_callback_ == true)
{
connection_callback_ = false;
return true;
}
return false;
}

std::unique_ptr<comm::TCPServer> server_;
socket_t client_fd_;
std::unique_ptr<TestableTcpServer> server_;

std::unique_ptr<comm::URStream<rtde_interface::RTDEPackage>> stream_;
std::unique_ptr<rtde_interface::RTDEParser> parser_;
Expand Down Expand Up @@ -138,8 +118,6 @@ class PipelineTest : public ::testing::Test
private:
std::condition_variable connect_cv_;
std::mutex connect_mutex_;

bool connection_callback_ = false;
};

TEST_F(PipelineTest, get_product_from_stopped_pipeline)
Expand All @@ -151,13 +129,13 @@ TEST_F(PipelineTest, get_product_from_stopped_pipeline)

TEST_F(PipelineTest, get_product_from_running_pipeline)
{
waitForConnectionCallback();
server_->waitForConnectionCallback();
pipeline_->run();

// RTDE package with timestamp
uint8_t data_package[] = { 0x00, 0x0c, 0x55, 0x01, 0x40, 0xbb, 0xbf, 0xdb, 0xa5, 0xe3, 0x53, 0xf7 };
size_t written;
server_->write(client_fd_, data_package, sizeof(data_package), written);
server_->write(data_package, sizeof(data_package), written);

std::unique_ptr<rtde_interface::RTDEPackage> urpackage;
std::chrono::milliseconds timeout{ 500 };
Expand All @@ -178,13 +156,13 @@ TEST_F(PipelineTest, get_product_from_running_pipeline)

TEST_F(PipelineTest, stop_pipeline)
{
waitForConnectionCallback();
server_->waitForConnectionCallback();
pipeline_->run();

// RTDE package with timestamp
uint8_t data_package[] = { 0x00, 0x0c, 0x55, 0x01, 0x40, 0xbb, 0xbf, 0xdb, 0xa5, 0xe3, 0x53, 0xf7 };
size_t written;
server_->write(client_fd_, data_package, sizeof(data_package), written);
server_->write(data_package, sizeof(data_package), written);

std::unique_ptr<rtde_interface::RTDEPackage> urpackage;
std::chrono::milliseconds timeout{ 500 };
Expand All @@ -206,13 +184,13 @@ TEST_F(PipelineTest, consumer_pipeline)
pipeline_.reset(
new comm::Pipeline<rtde_interface::RTDEPackage>(*producer_.get(), &consumer, "RTDE_PIPELINE", notifier_));
pipeline_->init();
waitForConnectionCallback();
server_->waitForConnectionCallback();
pipeline_->run();

// RTDE package with timestamp
uint8_t data_package[] = { 0x00, 0x0c, 0x55, 0x01, 0x40, 0xbb, 0xbf, 0xdb, 0xa5, 0xe3, 0x53, 0xf7 };
size_t written;
server_->write(client_fd_, data_package, sizeof(data_package), written);
server_->write(data_package, sizeof(data_package), written);

// Wait for data to be consumed
int max_retries = 3;
Expand All @@ -223,7 +201,7 @@ TEST_F(PipelineTest, consumer_pipeline)
{
break;
}
server_->write(client_fd_, data_package, sizeof(data_package), written);
server_->write(data_package, sizeof(data_package), written);
count++;
}
EXPECT_LT(count, max_retries);
Expand Down
37 changes: 5 additions & 32 deletions tests/test_producer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <gtest/gtest.h>
#include <chrono>
#include <condition_variable>
#include "test_utils.h"

#include <ur_client_library/comm/producer.h>
#include <ur_client_library/comm/stream.h>
Expand All @@ -44,8 +45,7 @@ class ProducerTest : public ::testing::Test
protected:
void SetUp()
{
server_.reset(new comm::TCPServer(60002));
server_->setConnectCallback(std::bind(&ProducerTest::connectionCallback, this, std::placeholders::_1));
server_.reset(new TestableTcpServer(60002));
server_->start();
}

Expand All @@ -55,34 +55,7 @@ class ProducerTest : public ::testing::Test
server_.reset();
}

void connectionCallback(const socket_t filedescriptor)
{
std::lock_guard<std::mutex> lk(connect_mutex_);
client_fd_ = filedescriptor;
connect_cv_.notify_one();
connection_callback_ = true;
}

bool waitForConnectionCallback(int milliseconds = 100)
{
std::unique_lock<std::mutex> lk(connect_mutex_);
if (connect_cv_.wait_for(lk, std::chrono::milliseconds(milliseconds)) == std::cv_status::no_timeout ||
connection_callback_ == true)
{
connection_callback_ = false;
return true;
}
return false;
}

std::unique_ptr<comm::TCPServer> server_;
socket_t client_fd_;

private:
std::condition_variable connect_cv_;
std::mutex connect_mutex_;

bool connection_callback_ = false;
std::unique_ptr<TestableTcpServer> server_;
};

TEST_F(ProducerTest, get_data_package)
Expand All @@ -94,13 +67,13 @@ TEST_F(ProducerTest, get_data_package)
comm::URProducer<rtde_interface::RTDEPackage> producer(stream, parser);

producer.setupProducer();
waitForConnectionCallback();
server_->waitForConnectionCallback();
producer.startProducer();

// RTDE package with timestamp
uint8_t data_package[] = { 0x00, 0x0c, 0x55, 0x01, 0x40, 0xbb, 0xbf, 0xdb, 0xa5, 0xe3, 0x53, 0xf7 };
size_t written;
server_->write(client_fd_, data_package, sizeof(data_package), written);
server_->write(data_package, sizeof(data_package), written);

std::vector<std::unique_ptr<rtde_interface::RTDEPackage>> products;
EXPECT_EQ(producer.tryGet(products), true);
Expand Down
Loading
Loading