Skip to content

Commit 8dbc463

Browse files
committed
Add test for program continuation
1 parent 6068f5d commit 8dbc463

2 files changed

Lines changed: 192 additions & 0 deletions

File tree

tests/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,22 @@ if (INTEGRATION_TESTS)
8282
TEST_SUFFIX _headless
8383
)
8484

85+
# ExternalControlProgram tests
86+
add_executable(external_control_program_tests_urcap test_external_control_program.cpp)
87+
target_link_libraries(external_control_program_tests_urcap PRIVATE ur_client_library::urcl GTest::gtest_main)
88+
gtest_add_tests(TARGET external_control_program_tests_urcap
89+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
90+
EXTRA_ARGS --headless false
91+
TEST_SUFFIX _urcap
92+
)
93+
add_executable(external_control_program_tests_headless test_external_control_program.cpp)
94+
target_link_libraries(external_control_program_tests_headless PRIVATE ur_client_library::urcl GTest::gtest_main)
95+
gtest_add_tests(TARGET external_control_program_tests_headless
96+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
97+
EXTRA_ARGS --headless true
98+
TEST_SUFFIX _headless
99+
)
100+
85101
# InstructionExecutor tests
86102
add_executable(instruction_executor_test_urcap test_instruction_executor.cpp)
87103
target_link_libraries(instruction_executor_test_urcap PRIVATE ur_client_library::urcl GTest::gtest_main)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// -- BEGIN LICENSE BLOCK ----------------------------------------------
2+
// Copyright 2026 Universal Robots A/S
3+
//
4+
// Redistribution and use in source and binary forms, with or without
5+
// modification, are permitted provided that the following conditions are met:
6+
//
7+
// * Redistributions of source code must retain the above copyright
8+
// notice, this list of conditions and the following disclaimer.
9+
//
10+
// * Redistributions in binary form must reproduce the above copyright
11+
// notice, this list of conditions and the following disclaimer in the
12+
// documentation and/or other materials provided with the distribution.
13+
//
14+
// * Neither the name of the {copyright_holder} nor the names of its
15+
// contributors may be used to endorse or promote products derived from
16+
// this software without specific prior written permission.
17+
//
18+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
// POSSIBILITY OF SUCH DAMAGE.
29+
// -- END LICENSE BLOCK ------------------------------------------------
30+
31+
#include <gtest/gtest.h>
32+
33+
#include "test_utils.h"
34+
#include "ur_client_library/example_robot_wrapper.h"
35+
#include "ur_client_library/log.h"
36+
37+
using namespace urcl;
38+
39+
const std::string SCRIPT_FILE = "../resources/external_control.urscript";
40+
const std::string OUTPUT_RECIPE = "resources/rtde_output_recipe.txt";
41+
const std::string INPUT_RECIPE = "resources/rtde_input_recipe.txt";
42+
std::string g_ROBOT_IP = "192.168.56.101";
43+
bool g_HEADLESS = true;
44+
45+
std::unique_ptr<ExampleRobotWrapper> g_my_robot;
46+
47+
class ExternalControlProgramTest : public ::testing::Test
48+
{
49+
public:
50+
// callback functions
51+
void connectionCallback(const socket_t filedescriptor)
52+
{
53+
std::lock_guard<std::mutex> lk(connect_mutex_);
54+
client_fd_ = filedescriptor;
55+
connect_cv_.notify_one();
56+
connection_callback_ = true;
57+
}
58+
59+
protected:
60+
static void SetUpTestSuite()
61+
{
62+
if (!(robotVersionLessThan(g_ROBOT_IP, "10.0.0") || g_HEADLESS))
63+
{
64+
GTEST_SKIP_("Running URCap tests for PolyScope X is currently not supported.");
65+
}
66+
}
67+
void SetUp() override
68+
{
69+
std::string modified_script_path = extendScript(SCRIPT_FILE);
70+
71+
g_my_robot = std::make_unique<ExampleRobotWrapper>(g_ROBOT_IP, OUTPUT_RECIPE, INPUT_RECIPE, g_HEADLESS,
72+
"external_control.urp", modified_script_path);
73+
if (!g_my_robot->isHealthy())
74+
{
75+
ASSERT_TRUE(g_my_robot->resendRobotProgram());
76+
ASSERT_TRUE(g_my_robot->waitForProgramRunning(500));
77+
}
78+
server_.reset(new comm::TCPServer(60005));
79+
server_->setConnectCallback(
80+
std::bind(&ExternalControlProgramTest::connectionCallback, this, std::placeholders::_1));
81+
server_->start();
82+
}
83+
84+
void TearDown() override
85+
{
86+
server_.reset();
87+
}
88+
89+
std::string extendScript(const std::string& script_path)
90+
{
91+
char modified_script_path[] = "urscript.XXXXXX";
92+
#ifdef _WIN32
93+
# define mkstemp _mktemp_s
94+
#endif
95+
std::ignore = mkstemp(modified_script_path);
96+
97+
std::ofstream ofs(modified_script_path);
98+
if (ofs.bad())
99+
{
100+
std::cout << "Failed to create temporary files" << std::endl;
101+
throw std::runtime_error("Failed to create temporary files");
102+
}
103+
std::ifstream in_file(script_path);
104+
std::string prog((std::istreambuf_iterator<char>(in_file)), (std::istreambuf_iterator<char>()));
105+
prog += "\nsocket_open(\"{{SERVER_IP_REPLACE}}\", 60005, \"test_socket\")\n";
106+
prog += "\nsleep(0.6)\n";
107+
prog += "\ntextmsg(\"sleeping done.\")\n";
108+
std::ofstream out_file;
109+
out_file.open(modified_script_path);
110+
out_file << prog;
111+
out_file.close();
112+
113+
return modified_script_path;
114+
}
115+
116+
bool waitForConnectionCallback(int milliseconds = 100)
117+
{
118+
std::unique_lock<std::mutex> lk(connect_mutex_);
119+
if (connect_cv_.wait_for(lk, std::chrono::milliseconds(milliseconds),
120+
[this]() { return connection_callback_ == true; }))
121+
{
122+
connection_callback_ = false;
123+
return true;
124+
}
125+
else
126+
{
127+
return false;
128+
}
129+
}
130+
std::unique_ptr<comm::TCPServer> server_;
131+
132+
private:
133+
std::condition_variable connect_cv_;
134+
std::mutex connect_mutex_;
135+
socket_t client_fd_ = INVALID_SOCKET;
136+
bool connection_callback_ = false;
137+
};
138+
139+
TEST_F(ExternalControlProgramTest, program_halts_on_timeout)
140+
{
141+
vector6d_t zeros = { 0, 0, 0, 0, 0, 0 };
142+
g_my_robot->getUrDriver()->writeJointCommand(zeros, comm::ControlMode::MODE_IDLE, RobotReceiveTimeout::millisec(200));
143+
EXPECT_FALSE(waitForConnectionCallback(1000));
144+
}
145+
146+
TEST_F(ExternalControlProgramTest, stop_control_does_not_halt_program)
147+
{
148+
vector6d_t zeros = { 0, 0, 0, 0, 0, 0 };
149+
g_my_robot->getUrDriver()->writeJointCommand(zeros, comm::ControlMode::MODE_IDLE, RobotReceiveTimeout::off());
150+
151+
// Make sure that we can stop the robot control, when robot receive timeout has been set off
152+
g_my_robot->getUrDriver()->stopControl();
153+
EXPECT_TRUE(waitForConnectionCallback(1000));
154+
}
155+
156+
int main(int argc, char* argv[])
157+
{
158+
::testing::InitGoogleTest(&argc, argv);
159+
160+
for (int i = 0; i < argc; i++)
161+
{
162+
if (std::string(argv[i]) == "--robot_ip" && i + 1 < argc)
163+
{
164+
g_ROBOT_IP = argv[i + 1];
165+
++i;
166+
}
167+
if (std::string(argv[i]) == "--headless" && i + 1 < argc)
168+
{
169+
std::string headless = argv[i + 1];
170+
g_HEADLESS = headless == "true" || headless == "1" || headless == "True" || headless == "TRUE";
171+
++i;
172+
}
173+
}
174+
175+
return RUN_ALL_TESTS();
176+
}

0 commit comments

Comments
 (0)