File: ament_add_pytest_test.cmake

package info (click to toggle)
ros2-ament-cmake 2.7.2-2
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 1,624 kB
  • sloc: python: 697; xml: 503; cpp: 144; ansic: 84; sh: 8; makefile: 5
file content (197 lines) | stat: -rw-r--r-- 6,608 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# Add a pytest test.
#
# :param testname: the name of the test
# :type testname: string
# :param path: the path to a file or folder where ``pytest`` should be invoked
#   on
# :type path: string
# :param NOCAPTURE: disable pytest output capturing.
#   Sets the pytest option '-s'.
# :type NOCAPTURE: option
# :param SKIP_TEST: if set mark the test as being skipped
# :type SKIP_TEST: option
# :param PYTHON_EXECUTABLE: Python executable used to run the test.
#   It defaults to the CMake executable target Python3::Interpreter.
# :type PYTHON_EXECUTABLE: string
# :param RUNNER: the path to the test runner script (default: see ament_add_test).
# :type RUNNER: string
# :param TIMEOUT: the test timeout in seconds,
#   default defined by ``ament_add_test()``
# :type TIMEOUT: integer
# :param WERROR: If ON, then treat warnings as errors. Default: OFF.
# :type WERROR: bool
# :param WORKING_DIRECTORY: the working directory for invoking the
#   command in, default defined by ``ament_add_test()``
# :type WORKING_DIRECTORY: string
# :param ENV: list of env vars to set; listed as ``VAR=value``
# :type ENV: list of strings
# :param APPEND_ENV: list of env vars to append if already set, otherwise set;
#   listed as ``VAR=value``
# :type APPEND_ENV: list of strings
# :param APPEND_LIBRARY_DIRS: list of library dirs to append to the appropriate
#   OS specific env var, a la LD_LIBRARY_PATH
# :type APPEND_LIBRARY_DIRS: list of strings
#
# @public
#
function(ament_add_pytest_test testname path)
  cmake_parse_arguments(ARG
    "NOCAPTURE;SKIP_TEST"
    "PYTHON_EXECUTABLE;RUNNER;TIMEOUT;WERROR;WORKING_DIRECTORY"
    "APPEND_ENV;APPEND_LIBRARY_DIRS;ENV"
    ${ARGN})
  if(ARG_UNPARSED_ARGUMENTS)
    message(FATAL_ERROR "ament_add_pytest_test() called with unused arguments: "
      "${ARG_UNPARSED_ARGUMENTS}")
  endif()

  # check arguments
  if(NOT IS_ABSOLUTE "${path}")
    set(path "${CMAKE_CURRENT_SOURCE_DIR}/${path}")
  endif()
  # only check existence of path if it doesn't contain generator expressions
  string(FIND "${path}" "$<" index)
  if(index EQUAL -1 AND NOT EXISTS "${path}")
    message(FATAL_ERROR
      "ament_add_pytest_test() the path '${path}' does not exist")
  endif()
  if(NOT ARG_PYTHON_EXECUTABLE)
    set(ARG_PYTHON_EXECUTABLE Python3::Interpreter)
  endif()

  get_executable_path(python_interpreter "${ARG_PYTHON_EXECUTABLE}" BUILD)

  # ensure pytest is available
  ament_has_pytest(has_pytest QUIET PYTHON_EXECUTABLE "${ARG_PYTHON_EXECUTABLE}")
  if(NOT has_pytest)
    message(WARNING
      "The Python module 'pytest' was not found, pytests cannot be run. "
      "On Linux, install the 'python3-pytest' package. "
      "On other platforms, install 'pytest' using pip.")
    return()
  endif()

  set(result_file "${AMENT_TEST_RESULTS_DIR}/${PROJECT_NAME}/${testname}.xunit.xml")
  set(cmd
    "${python_interpreter}"
    "-u"  # unbuffered stdout and stderr
    "-m" "pytest"
    "${path}"
    # store last failed tests
    "-o" "cache_dir=${CMAKE_CURRENT_BINARY_DIR}/ament_cmake_pytest/${testname}/.cache"
    # junit arguments
    "--junit-xml=${result_file}"
    "--junit-prefix=${PROJECT_NAME}"
  )

  set(ARG_ENV PYTHONDONTWRITEBYTECODE=1 ${ARG_ENV})

  if(ARG_NOCAPTURE)
    # disable output capturing
    list(APPEND cmd "-s")
  endif()

  if(ARG_WERROR)
    # treat warnings as errors
    list(APPEND cmd "-We")
  endif()

  # enable pytest coverage by default if the package test_depends on python3-pytest-cov
  if("python3-pytest-cov" IN_LIST ${PROJECT_NAME}_TEST_DEPENDS)
    set(coverage_default ON)
  else()
    set(coverage_default OFF)
  endif()
  option(AMENT_CMAKE_PYTEST_WITH_COVERAGE
    "Generate coverage information for Python tests"
    ${coverage_default})

  if(AMENT_CMAKE_PYTEST_WITH_COVERAGE)
    # get pytest-cov version, if available
    ament_get_pytest_cov_version(pytest_cov_version
      PYTHON_EXECUTABLE "${ARG_PYTHON_EXECUTABLE}"
    )
    if(NOT pytest_cov_version)
      message(WARNING
        "The Python module 'pytest-cov' was not found, test coverage will not be produced. "
        "On Linux, install the 'python3-pytest-cov' package. "
        "On other platforms, install 'pytest-cov' using pip.")
    else()
      set(coverage_directory "${CMAKE_CURRENT_BINARY_DIR}/pytest_cov/${testname}")
      file(MAKE_DIRECTORY "${coverage_directory}")

      list(APPEND cmd
        "--cov=${CMAKE_CURRENT_SOURCE_DIR}"
        "--cov-report=html:${coverage_directory}/coverage.html"
        "--cov-report=xml:${coverage_directory}/coverage.xml"
      )

      if(pytest_cov_version VERSION_LESS "2.5.0")
        message(WARNING
          "Test coverage will be produced, but will not contain branch coverage information, "
          "because the pytest extension 'cov' does not support it "
          "(need 2.5.0, found '${pytest_cov_version}').")
      else()
        list(APPEND cmd "--cov-branch")
      endif()

      list(APPEND ARG_ENV "COVERAGE_FILE=${coverage_directory}/.coverage")
    endif()
  endif()

  if(ARG_ENV)
    set(ARG_ENV "ENV" ${ARG_ENV})
  endif()
  if(ARG_APPEND_ENV)
    set(ARG_APPEND_ENV "APPEND_ENV" ${ARG_APPEND_ENV})
  endif()
  if(ARG_APPEND_LIBRARY_DIRS)
    set(ARG_APPEND_LIBRARY_DIRS "APPEND_LIBRARY_DIRS" ${ARG_APPEND_LIBRARY_DIRS})
  endif()
  if(ARG_RUNNER)
    set(ARG_RUNNER "RUNNER" ${ARG_RUNNER})
  endif()
  if(ARG_TIMEOUT)
    set(ARG_TIMEOUT "TIMEOUT" "${ARG_TIMEOUT}")
  endif()
  if(ARG_WORKING_DIRECTORY)
    set(ARG_WORKING_DIRECTORY "WORKING_DIRECTORY" "${ARG_WORKING_DIRECTORY}")
  endif()
  if(ARG_SKIP_TEST)
    set(ARG_SKIP_TEST "SKIP_TEST")
  endif()

  ament_add_test(
    "${testname}"
    COMMAND ${cmd}
    OUTPUT_FILE "${CMAKE_BINARY_DIR}/ament_cmake_pytest/${testname}.txt"
    RESULT_FILE "${result_file}"
    ${ARG_RUNNER}
    ${ARG_SKIP_TEST}
    ${ARG_ENV}
    ${ARG_APPEND_ENV}
    ${ARG_APPEND_LIBRARY_DIRS}
    ${ARG_TIMEOUT}
    ${ARG_WORKING_DIRECTORY}
  )
  set_tests_properties(
    "${testname}"
    PROPERTIES
    LABELS "pytest"
  )
endfunction()