Skip to content

Commit de6faf8

Browse files
authored
Added a test to check all robot model parametrizations (UniversalRobots#505)
This PR adds a workflow to check the robot model as reported from the primary interface with the model argument provided to `start_ursim.sh`. This also fixes passing ur7e or ur12e to PolyScope 5. To reduce build resources, this PR splits the integration tests into a "build" stage and two "test" stages utilizing that build. This probably doesn't save any time, but build resources and makes handling a failed build easier.
1 parent 3f95311 commit de6faf8

6 files changed

Lines changed: 251 additions & 27 deletions

File tree

.github/workflows/ci.yml

Lines changed: 171 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,48 @@ on:
99
- cron: '38 2 * * *'
1010

1111
jobs:
12-
build:
12+
ubuntu_build:
13+
name: ubuntu_build
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v6
17+
- name: Install build-tools
18+
run: sudo apt-get update && sudo apt-get install -y build-essential cmake python3-pandas python3-lxml
19+
- name: configure
20+
run: >
21+
mkdir build &&
22+
cd build &&
23+
cmake ..
24+
-DBUILDING_TESTS=1
25+
-DINTEGRATION_TESTS=1
26+
-DWITH_ASAN=ON
27+
-DPRIMARY_CLIENT_STRICT_PARSING_=ON
28+
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON
29+
-DCHECK_RTDE_DOCS_RECIPE=ON
30+
env:
31+
CXXFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
32+
CFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
33+
LDFLAGS: -fprofile-arcs -ftest-coverage
34+
- name: build
35+
id: build
36+
run: cmake --build build --config Debug
37+
- name: Archive CMake build directory
38+
if: steps.build.outcome == 'success'
39+
run: tar -czf build.tar.gz build
40+
- name: Upload CMake build archive
41+
if: steps.build.outcome == 'success'
42+
uses: actions/upload-artifact@v7
43+
with:
44+
path: build.tar.gz
45+
if-no-files-found: error
46+
retention-days: 5
47+
archive: false
48+
49+
run_tests:
1350
timeout-minutes: 60
1451
runs-on: ubuntu-latest
15-
name: build (${{matrix.env.URSIM_VERSION}}-${{matrix.env.ROBOT_MODEL}})
52+
name: run_tests (${{matrix.env.URSIM_VERSION}}-${{matrix.env.ROBOT_MODEL}})
53+
needs: ubuntu_build
1654
strategy:
1755
fail-fast: false
1856
matrix:
@@ -46,8 +84,6 @@ jobs:
4684
ROBOT_MODEL: ${{matrix.env.ROBOT_MODEL}}
4785
URSIM_VERSION: ${{matrix.env.URSIM_VERSION}}
4886
PROGRAM_FOLDER: ${{matrix.env.PROGRAM_FOLDER}}
49-
- name: install-pips
50-
run: pip install pandas lxml
5187
- id: check_polyscopex
5288
run: |
5389
if [[ "${{matrix.env.URSIM_VERSION}}" == "10."* ]]; then
@@ -57,35 +93,26 @@ jobs:
5793
fi
5894
- name: setup chrome
5995
uses: browser-actions/setup-chrome@v2
60-
- name: configure
61-
run: >
62-
mkdir build &&
63-
cd build &&
64-
cmake ..
65-
-DBUILDING_TESTS=1
66-
-DINTEGRATION_TESTS=1
67-
-DWITH_ASAN=ON
68-
-DPRIMARY_CLIENT_STRICT_PARSING=ON
69-
-DCMAKE_COMPILE_WARNING_AS_ERROR=ON
70-
-DCHECK_RTDE_DOCS_RECIPE=ON
71-
env:
72-
CXXFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
73-
CFLAGS: -g -O2 -fprofile-arcs -ftest-coverage
74-
LDFLAGS: -fprofile-arcs -ftest-coverage
75-
- name: build
76-
id: build
77-
run: cmake --build build --config Debug
96+
- name: Download CMake build archive
97+
uses: actions/download-artifact@v8
98+
with:
99+
name: build.tar.gz
100+
- name: Extract CMake build directory
101+
run: tar -xzf build.tar.gz
78102
- name: Create folder for test artifacts
79103
run: mkdir -p test_artifacts
80104
- name: Access PolyScope
81105
if: ${{ steps.check_polyscopex.outputs.is_polyscopex == 'true' }}
82106
run: chrome --no-sandbox --disable-settuid-sandbox --headless=new 192.168.56.101 &
107+
- name: Install Python dependencies
108+
run: sudo apt-get update && sudo apt-get install -y python3-pandas python3-lxml
109+
- name: Generate rtde outputs lists
110+
run: python3 tests/resources/generate_rtde_outputs.py
83111
- name: test
84112
run: cd build && ctest --output-on-failure --output-junit junit.xml
85113
env:
86114
URSIM_VERSION: ${{matrix.env.URSIM_VERSION}}
87115
- name: Upload test results to Codecov
88-
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
89116
uses: codecov/codecov-action@v6
90117
with:
91118
fail_ci_if_error: true
@@ -96,13 +123,10 @@ jobs:
96123
- name: run examples
97124
run: ./run_examples.sh "192.168.56.101" 1
98125
- name: install gcovr
99-
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
100126
run: sudo apt-get install -y gcovr
101127
- name: gcovr
102-
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
103128
run: cd build && gcovr -r .. --xml coverage.xml --gcov-ignore-parse-errors negative_hits.warn_once_per_file --exclude "../3rdparty"
104129
- name: Upload coverage reports to Codecov with GitHub Action
105-
if: ${{ !cancelled() && steps.build.outcome == 'success' }}
106130
uses: codecov/codecov-action@v6
107131
with:
108132
fail_ci_if_error: true
@@ -150,6 +174,127 @@ jobs:
150174
if-no-files-found: warn
151175
retention-days: 10
152176

177+
robot_model_check:
178+
timeout-minutes: 60
179+
runs-on: ubuntu-latest
180+
needs: ubuntu_build
181+
name: check_model (${{matrix.env.URSIM_VERSION}}-${{matrix.env.ROBOT_MODEL}})
182+
strategy:
183+
fail-fast: false
184+
matrix:
185+
env:
186+
- ROBOT_MODEL: 'ur3'
187+
URSIM_VERSION: '3.14.3'
188+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/cb3'
189+
- ROBOT_MODEL: 'ur5'
190+
URSIM_VERSION: '3.15.8'
191+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/cb3'
192+
- ROBOT_MODEL: 'ur10'
193+
URSIM_VERSION: '3.15.8'
194+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/cb3'
195+
- ROBOT_MODEL: 'ur3e'
196+
URSIM_VERSION: '5.9.4'
197+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
198+
- ROBOT_MODEL: 'ur5e'
199+
URSIM_VERSION: '5.12.8'
200+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
201+
- ROBOT_MODEL: 'ur7e'
202+
URSIM_VERSION: '5.22.2'
203+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
204+
- ROBOT_MODEL: 'ur10e'
205+
URSIM_VERSION: '5.15.2'
206+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
207+
- ROBOT_MODEL: 'ur12e'
208+
URSIM_VERSION: '5.25.1'
209+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
210+
- ROBOT_MODEL: 'ur16e'
211+
URSIM_VERSION: '5.25.1'
212+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
213+
- ROBOT_MODEL: 'ur8long'
214+
URSIM_VERSION: '5.25.1'
215+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
216+
- ROBOT_MODEL: 'ur15'
217+
URSIM_VERSION: '5.25.1'
218+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
219+
- ROBOT_MODEL: 'ur18'
220+
URSIM_VERSION: '5.25.1'
221+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
222+
- ROBOT_MODEL: 'ur20'
223+
URSIM_VERSION: '5.25.1'
224+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
225+
- ROBOT_MODEL: 'ur30'
226+
URSIM_VERSION: '5.25.1'
227+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/e-series'
228+
- ROBOT_MODEL: 'ur3e'
229+
URSIM_VERSION: '10.11.0'
230+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
231+
- ROBOT_MODEL: 'ur5e'
232+
URSIM_VERSION: '10.11.0'
233+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
234+
- ROBOT_MODEL: 'ur7e'
235+
URSIM_VERSION: '10.11.0'
236+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
237+
- ROBOT_MODEL: 'ur10e'
238+
URSIM_VERSION: '10.11.0'
239+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
240+
- ROBOT_MODEL: 'ur12e'
241+
URSIM_VERSION: '10.12.1'
242+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
243+
- ROBOT_MODEL: 'ur16e'
244+
URSIM_VERSION: '10.12.1'
245+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
246+
- ROBOT_MODEL: 'ur8long'
247+
URSIM_VERSION: '10.12.1'
248+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
249+
- ROBOT_MODEL: 'ur15'
250+
URSIM_VERSION: '10.12.1'
251+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
252+
- ROBOT_MODEL: 'ur18'
253+
URSIM_VERSION: '10.12.1'
254+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
255+
- ROBOT_MODEL: 'ur20'
256+
URSIM_VERSION: '10.12.1'
257+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
258+
- ROBOT_MODEL: 'ur30'
259+
URSIM_VERSION: '10.12.1'
260+
PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex'
261+
262+
steps:
263+
- uses: actions/checkout@v6
264+
- name: start ursim
265+
run: |
266+
scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER -d -f DISABLED
267+
env:
268+
DOCKER_RUN_OPTS: --network ursim_net
269+
ROBOT_MODEL: ${{matrix.env.ROBOT_MODEL}}
270+
URSIM_VERSION: ${{matrix.env.URSIM_VERSION}}
271+
PROGRAM_FOLDER: ${{matrix.env.PROGRAM_FOLDER}}
272+
- name: Download CMake build archive
273+
uses: actions/download-artifact@v8
274+
with:
275+
name: build.tar.gz
276+
- name: Extract CMake build directory
277+
run: tar -xzf build.tar.gz
278+
- name: inspect build folder
279+
run: ls -la build && ls -la build/tests
280+
- name: test robot type
281+
run: cd build && ctest -R PrimaryClientTest.test_robot_type --verbose --output-junit junit.xml
282+
env:
283+
URSIM_VERSION: ${{matrix.env.URSIM_VERSION}}
284+
ROBOT_MODEL: ${{matrix.env.ROBOT_MODEL}}
285+
- name: install gcovr
286+
run: sudo apt-get install -y gcovr
287+
- name: gcovr
288+
run: cd build && gcovr -r .. --xml coverage.xml --gcov-ignore-parse-errors negative_hits.warn_once_per_file --exclude "../3rdparty"
289+
- name: Upload coverage reports to Codecov with GitHub Action
290+
uses: codecov/codecov-action@v6
291+
with:
292+
fail_ci_if_error: true
293+
files: build/coverage.xml
294+
flags: check_version_${{ matrix.env.ROBOT_MODEL }}-${{ matrix.env.URSIM_VERSION }}
295+
env:
296+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
297+
153298
test_start_ursim:
154299
runs-on: ubuntu-latest
155300
steps:

include/ur_client_library/helpers.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,22 @@ void clampToUnitRange(std::array<T, N>& values)
169169
*/
170170
RobotSeries robotSeriesFromTypeAndVersion(const RobotType type, const VersionInformation& version);
171171

172+
/*!
173+
* \brief Get the robot type from a string.
174+
*
175+
* The \c RobotType enum has no dedicated entries for UR7 and UR12, so "ur7e" is mapped to
176+
* \c RobotType::UR5 and "ur12e" is mapped to \c RobotType::UR10, matching what the robot
177+
* reports over the primary interface.
178+
*
179+
* \param robot_type_str The string representation of the robot type as used in the start_ursim.sh
180+
* script. Must be all lower-case, e.g. "ur3e", "ur5", "ur10e", "ur16e", "ur7e", "ur15", "ur30",
181+
* "ur8long".
182+
*
183+
* \throws std::invalid_argument if \p robot_type_str does not match a known robot type.
184+
*
185+
* \returns The robot type corresponding to the given string.
186+
*/
187+
RobotType robotTypeFromString(const std::string& robot_type_str);
188+
172189
} // namespace urcl
173190
#endif // ifndef UR_CLIENT_LIBRARY_HELPERS_H_INCLUDED

scripts/start_ursim.sh

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,13 @@ strip_robot_model()
129129
if [[ "$robot_model" = @(ur3e|ur5e|ur10e|ur16e) ]]; then
130130
ROBOT_MODEL=$(echo "${ROBOT_MODEL:0:$((${#ROBOT_MODEL}-1))}")
131131
elif [[ "$robot_model" = @(ur7e|ur12e) ]]; then
132-
ROBOT_MODEL=$(echo "${ROBOT_MODEL:0:$((${#ROBOT_MODEL}-1))}e")
132+
# PolyScope X uses UR7e and UR12e, but PolyScope 5 uses UR7 and UR12. So we
133+
# need to strip the "e" for PolyScope 5
134+
if [[ "$robot_series" == "polyscopex" ]]; then
135+
ROBOT_MODEL=$(echo "${ROBOT_MODEL:0:$((${#ROBOT_MODEL}-1))}e")
136+
else
137+
ROBOT_MODEL=$(echo "${ROBOT_MODEL:0:$((${#ROBOT_MODEL}-1))}")
138+
fi
133139
fi
134140
fi
135141
}

src/helpers.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@
3535
#include <cstring>
3636
#include <fstream>
3737
#include <iostream>
38+
#include <stdexcept>
3839
#include <thread>
40+
#include <unordered_map>
3941

4042
// clang-format off
4143
// We want to keep the URL in one line to avoid formatting issues. This will make it easier to
@@ -203,4 +205,24 @@ RobotSeries robotSeriesFromTypeAndVersion(const RobotType type, const VersionInf
203205
return RobotSeries::UNDEFINED;
204206
}
205207

208+
RobotType robotTypeFromString(const std::string& robot_type_str)
209+
{
210+
// RobotType has no dedicated entries for UR7/UR12, so UR7e and UR12e are mapped to their
211+
// closest siblings UR5 and UR10 respectively, matching what the robot reports over primary.
212+
static const std::unordered_map<std::string, RobotType> string_to_robot_type{
213+
{ "ur3", RobotType::UR3 }, { "ur3e", RobotType::UR3 }, { "ur5", RobotType::UR5 },
214+
{ "ur5e", RobotType::UR5 }, { "ur7e", RobotType::UR5 }, { "ur10", RobotType::UR10 },
215+
{ "ur10e", RobotType::UR10 }, { "ur12e", RobotType::UR10 }, { "ur16e", RobotType::UR16 },
216+
{ "ur15", RobotType::UR15 }, { "ur18", RobotType::UR18 }, { "ur20", RobotType::UR20 },
217+
{ "ur30", RobotType::UR30 }, { "ur8long", RobotType::UR8LONG },
218+
};
219+
220+
const auto it = string_to_robot_type.find(robot_type_str);
221+
if (it == string_to_robot_type.end())
222+
{
223+
throw std::invalid_argument("Unknown robot type: " + robot_type_str);
224+
}
225+
return it->second;
226+
}
227+
206228
} // namespace urcl

tests/test_primary_client.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,32 @@ TEST_F(PrimaryClientTest, test_configuration_data)
267267
EXPECT_NE(client_->getRobotType(), RobotType::UNDEFINED);
268268
}
269269

270+
TEST_F(PrimaryClientTest, test_robot_type)
271+
{
272+
if (std::getenv("ROBOT_MODEL") == nullptr)
273+
{
274+
GTEST_SKIP() << "ROBOT_MODEL environment variable not set. Skipping test.";
275+
}
276+
// When this test runs as the only test against a freshly started URSim (as it does in the
277+
// robot_model_check CI matrix), the robot may still be initialising and drop the first
278+
// primary connection. Use a short socket reconnection interval so that the producer retries
279+
// quickly, and a generous outer timeout so we can ride out URSim's full warm-up.
280+
constexpr auto reconnection_time = std::chrono::milliseconds(500);
281+
EXPECT_NO_THROW(client_->start(/*max_num_tries=*/0, reconnection_time));
282+
283+
// Wait until we have received configuration data so that the robot type is known.
284+
const auto start_time = std::chrono::system_clock::now();
285+
const auto timeout = std::chrono::seconds(60);
286+
while (client_->getConfigurationData() == nullptr && std::chrono::system_clock::now() - start_time < timeout)
287+
{
288+
std::this_thread::sleep_for(std::chrono::milliseconds(100));
289+
}
290+
ASSERT_NE(client_->getConfigurationData(), nullptr);
291+
const std::string robot_model_env = std::getenv("ROBOT_MODEL");
292+
const RobotType expected_robot_type = robotTypeFromString(robot_model_env);
293+
EXPECT_EQ(client_->getRobotType(), expected_robot_type);
294+
}
295+
270296
TEST_F(PrimaryClientTest, test_kinematics_info)
271297
{
272298
EXPECT_NO_THROW(client_->start());

tests/test_start_ursim.bats

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,10 +415,18 @@ setup() {
415415

416416
strip_robot_model ur7e e-series
417417
echo "Robot model is: $ROBOT_MODEL"
418+
[ "$ROBOT_MODEL" = "UR7" ]
419+
420+
strip_robot_model ur7e polyscopex
421+
echo "Robot model is: $ROBOT_MODEL"
418422
[ "$ROBOT_MODEL" = "UR7e" ]
419423

420424
strip_robot_model ur12e e-series
421425
echo "Robot model is: $ROBOT_MODEL"
426+
[ "$ROBOT_MODEL" = "UR12" ]
427+
428+
strip_robot_model ur12e polyscopex
429+
echo "Robot model is: $ROBOT_MODEL"
422430
[ "$ROBOT_MODEL" = "UR12e" ]
423431
}
424432

0 commit comments

Comments
 (0)