├── .github └── workflows │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── binder-logo.svg ├── binder └── environment.yml ├── environment-dev.yml ├── images ├── completion.gif ├── completion2.gif ├── debugger.gif ├── keywords.gif ├── python.gif └── xeus-robot.svg ├── notebooks ├── Interactive_Python_XKCD.ipynb ├── SeleniumScreenshots.ipynb └── xrobot.ipynb ├── share └── jupyter │ └── kernels │ └── xrobot │ ├── kernel.js │ ├── kernel.json.in │ ├── logo-32x32.png │ └── logo-64x64.png ├── src ├── main.cpp ├── xdebugger.cpp ├── xdebugger.hpp ├── xeus_robot_config.hpp ├── xinternal_utils.cpp ├── xinternal_utils.hpp ├── xinterpreter.cpp ├── xinterpreter.hpp ├── xrobodebug_client.cpp ├── xrobodebug_client.hpp ├── xrobot_extension.cpp ├── xtraceback.cpp └── xtraceback.hpp └── test ├── CMakeLists.txt ├── copyGTest.cmake.in ├── downloadGTest.cmake.in ├── test_xrobot.cpp ├── test_xrobot_kernel.py ├── xeus_client.cpp └── xeus_client.hpp /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | defaults: 13 | run: 14 | shell: bash -l {0} 15 | 16 | 17 | jobs: 18 | test-unix: 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | os: [ubuntu-18.04, ubuntu-20.04, macos-10.15, macos-11] 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | 30 | - name: Get number of CPU cores 31 | uses: SimenB/github-actions-cpu-cores@v1 32 | 33 | - name: install mamba 34 | uses: mamba-org/provision-with-micromamba@main 35 | with: 36 | environment-file: environment-dev.yml 37 | environment-name: xeus-robot 38 | 39 | - name: Make build directory 40 | run: mkdir build 41 | 42 | - name: cmake configure 43 | run: | 44 | cmake .. \ 45 | -D CMAKE_PREFIX_PATH=$CONDA_PREFIX \ 46 | -D CMAKE_INSTALL_PREFIX=$CONDA_PREFIX \ 47 | -D CMAKE_PREFIX_PATH=$CONDA_PREFIX \ 48 | -D CMAKE_INSTALL_LIBDIR=lib \ 49 | -D PYTHON_EXECUTABLE=`which python` \ 50 | -D CMAKE_C_COMPILER=$CC \ 51 | -D CMAKE_CXX_COMPILER=$CXX \ 52 | -D CMAKE_CXX_STANDARD=17 \ 53 | -D XROB_BUILD_XROBOT_EXTENSION=ON \ 54 | -D XROB_DOWNLOAD_GTEST=ON 55 | working-directory: build 56 | 57 | - name: Install 58 | run: make -j ${{ steps.cpu-cores.outputs.count }} install 59 | working-directory: build 60 | 61 | - name: Test xeus-robot C++ 62 | run: ./test_xeus_robot 63 | timeout-minutes: 4 64 | working-directory: build/test 65 | 66 | - name: Test xeus-robot Python 67 | run: python test_xrobot_kernel.py -vvv 68 | working-directory: test 69 | 70 | test-win: 71 | 72 | runs-on: ${{ matrix.os }} 73 | 74 | strategy: 75 | fail-fast: false 76 | matrix: 77 | os: [windows-2019] 78 | 79 | steps: 80 | - uses: actions/checkout@v2 81 | 82 | - name: install mamba 83 | uses: mamba-org/provision-with-micromamba@main 84 | with: 85 | environment-file: environment-dev.yml 86 | environment-name: xeus-robot 87 | 88 | - name: micromamba shell hook 89 | shell: powershell 90 | run: | 91 | micromamba shell hook -s cmd.exe -p C:\Users\runneradmin\micromamba-root 92 | 93 | - name: Make build directory 94 | run: mkdir build 95 | 96 | - name: cmake configure 97 | shell: cmd 98 | run: | 99 | call C:\Users\runneradmin\micromamba-root\condabin\micromamba.bat activate xeus-robot 100 | cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -DXROB_DOWNLOAD_GTEST=ON -DCMAKE_INSTALL_PREFIX="%CONDA_PREFIX%\Library" -DXEXTRA_JUPYTER_DATA_DIR=%CONDA_PREFIX%\share\jupyter -DXROB_BUILD_XROBOT_EXTENSION=ON -DXEUS_PYTHONHOME_RELPATH=..\\ -Dgtest_force_shared_crt=ON -DCMAKE_CXX_FLAGS=/D_SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING 101 | working-directory: build 102 | 103 | - name: build 104 | shell: cmd 105 | run: | 106 | call C:\Users\runneradmin\micromamba-root\condabin\micromamba.bat activate xeus-robot 107 | set CL=/MP 108 | nmake install 109 | working-directory: build 110 | 111 | - name: Test xeus-robot C++ 112 | shell: cmd 113 | run: | 114 | call C:\Users\runneradmin\micromamba-root\condabin\micromamba.bat activate xeus-robot 115 | test_xeus_robot 116 | timeout-minutes: 4 117 | working-directory: build/test 118 | 119 | - name: Test xeus-robot Python 120 | shell: cmd 121 | run: | 122 | call C:\Users\runneradmin\micromamba-root\condabin\micromamba.bat activate xeus-robot 123 | python test_xrobot_kernel.py -vvv 124 | working-directory: test 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | test/__pycache__/ 3 | build/* 4 | share/jupyter/kernels/xrobot/kernel.json 5 | notebooks/.ipynb_checkpoints/* 6 | *Untitled*.ipynb* 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Copyright (c) 2016, Martin Renou, Johan Mabille, Sylvain Corlay, and # 3 | # Wolf Vollprecht # 4 | # Copyright (c) 2016, QuantStack # 5 | # # 6 | # Distributed under the terms of the BSD 3-Clause License. # 7 | # # 8 | # The full license is in the file LICENSE, distributed with this software. # 9 | ############################################################################ 10 | 11 | cmake_minimum_required(VERSION 3.4.3) 12 | project(xeus-robot) 13 | 14 | set(XEUS_ROBOT_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src) 15 | 16 | # Versionning 17 | # =========== 18 | 19 | file(STRINGS "${XEUS_ROBOT_SRC_DIR}/xeus_robot_config.hpp" xrob_version_defines 20 | REGEX "#define XROB_VERSION_(MAJOR|MINOR|PATCH)") 21 | foreach (ver ${xrob_version_defines}) 22 | if (ver MATCHES "#define XROB_VERSION_(MAJOR|MINOR|PATCH) +([^ ]+)$") 23 | set(XROB_VERSION_${CMAKE_MATCH_1} "${CMAKE_MATCH_2}" CACHE INTERNAL "") 24 | endif () 25 | endforeach () 26 | set(${PROJECT_NAME}_VERSION 27 | ${XROB_VERSION_MAJOR}.${XROB_VERSION_MINOR}.${XROB_VERSION_PATCH}) 28 | message(STATUS "Building xeus-robot v${${PROJECT_NAME}_VERSION}") 29 | 30 | # Configuration 31 | # ============= 32 | 33 | include(GNUInstallDirs) 34 | 35 | if (NOT DEFINED XROBOT_KERNELSPEC_PATH) 36 | set(XROBOT_KERNELSPEC_PATH "${CMAKE_INSTALL_FULL_BINDIR}/") 37 | endif () 38 | 39 | configure_file ( 40 | "${CMAKE_CURRENT_SOURCE_DIR}/share/jupyter/kernels/xrobot/kernel.json.in" 41 | "${CMAKE_CURRENT_SOURCE_DIR}/share/jupyter/kernels/xrobot/kernel.json" 42 | ) 43 | 44 | # Build options 45 | # ============= 46 | 47 | # Compilation options 48 | OPTION(XROB_DISABLE_ARCH_NATIVE "disable -march=native flag" OFF) 49 | OPTION(XROB_DISABLE_TUNE_GENERIC "disable -mtune=generic flag" OFF) 50 | OPTION(XROB_ENABLE_PYPI_WARNING "Enable warning on PyPI wheels" OFF) 51 | 52 | OPTION(XROB_BUILD_XROBOT_EXECUTABLE "Build the xrobot executable" ON) 53 | OPTION(XROB_BUILD_XROBOT_EXTENSION "Build the xrobot extension module" OFF) 54 | 55 | OPTION(XROB_USE_SHARED_XEUS_PYTHON "Link xrobot and xrobot_extension with the xeus-python shared library (instead of the static library)" ON) 56 | 57 | # Test options 58 | OPTION(XROB_BUILD_TESTS "xeus-robot test suite" OFF) 59 | OPTION(XROB_DOWNLOAD_GTEST "build gtest from downloaded sources" OFF) 60 | 61 | # Dependencies 62 | # ============ 63 | 64 | set(xeus_python_REQUIRED_VERSION 0.15.2) 65 | set(pybind11_REQUIRED_VERSION 2.6.1) 66 | set(pybind11_json_REQUIRED_VERSION 0.2.8) 67 | 68 | if (NOT TARGET xeus-python AND NOT TARGET xeus-python-static) 69 | find_package(xeus-python ${xeus_python_REQUIRED_VERSION} REQUIRED) 70 | endif () 71 | if (NOT TARGET pybind11::headers) 72 | find_package(pybind11 ${pybind11_REQUIRED_VERSION} REQUIRED) 73 | endif () 74 | if (NOT TARGET pybind11_json) 75 | find_package(pybind11_json ${pybind11_json_REQUIRED_VERSION} REQUIRED) 76 | endif () 77 | 78 | # Flags 79 | # ===== 80 | 81 | include(CheckCXXCompilerFlag) 82 | 83 | if (MSVC) 84 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4251 /wd4141") 85 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4018 /wd4267 /wd4715 /wd4146 /wd4129") 86 | endif () 87 | 88 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Intel") 89 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused-parameter -Wextra -Wreorder") 90 | if (XROB_DISABLE_ARCH_NATIVE) 91 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mtune=generic") 92 | elseif (XROB_DISABLE_TUNE_GENERIC) 93 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 94 | else () 95 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native") 96 | endif () 97 | 98 | CHECK_CXX_COMPILER_FLAG("-std=c++14" HAS_CPP14_FLAG) 99 | 100 | if (HAS_CPP14_FLAG) 101 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") 102 | else () 103 | message(FATAL_ERROR "Unsupported compiler -- xeus requires C++14 support!") 104 | endif () 105 | endif () 106 | 107 | # Source files 108 | # ============ 109 | 110 | set(XROBOT_SRC 111 | src/main.cpp 112 | src/xinternal_utils.hpp 113 | src/xinternal_utils.cpp 114 | src/xinterpreter.hpp 115 | src/xinterpreter.cpp 116 | src/xeus_robot_config.hpp 117 | src/xdebugger.hpp 118 | src/xdebugger.cpp 119 | src/xrobodebug_client.hpp 120 | src/xrobodebug_client.cpp 121 | src/xtraceback.hpp 122 | src/xtraceback.cpp 123 | ) 124 | 125 | set(XROBOT_EXTENSION_SRC 126 | src/xrobot_extension.cpp 127 | src/xinternal_utils.hpp 128 | src/xinternal_utils.cpp 129 | src/xinterpreter.hpp 130 | src/xinterpreter.cpp 131 | src/xeus_robot_config.hpp 132 | src/xdebugger.hpp 133 | src/xdebugger.cpp 134 | src/xrobodebug_client.hpp 135 | src/xrobodebug_client.cpp 136 | src/xtraceback.hpp 137 | src/xtraceback.cpp 138 | ) 139 | 140 | # Targets and link - Macros 141 | # ========================= 142 | 143 | string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE) 144 | 145 | set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib; ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") 146 | 147 | include(CheckCXXCompilerFlag) 148 | 149 | macro(xrob_set_common_options target_name) 150 | if (MSVC) 151 | target_compile_options(${target_name} PUBLIC /wd4251 /wd4141) 152 | target_compile_options(${target_name} PUBLIC /wd4018 /wd4267 /wd4715 /wd4146 /wd4129) 153 | endif () 154 | 155 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR 156 | CMAKE_CXX_COMPILER_ID MATCHES "GNU" OR 157 | CMAKE_CXX_COMPILER_ID MATCHES "Intel") 158 | 159 | target_compile_options(${target_name} PUBLIC -Wunused-parameter -Wextra -Wreorder) 160 | 161 | # Mtune generic/native 162 | if (XROB_DISABLE_ARCH_NATIVE AND NOT XROB_DISABLE_TUNE_GENERIC) 163 | target_compile_options(${target_name} PUBLIC -mtune=generic) 164 | elseif (XROB_DISABLE_TUNE_GENERIC) 165 | else () 166 | target_compile_options(${target_name} PUBLIC -march=native) 167 | endif () 168 | 169 | # C++14 flag 170 | CHECK_CXX_COMPILER_FLAG("-std=c++14" HAS_CPP14_FLAG) 171 | if (HAS_CPP14_FLAG) 172 | target_compile_features(${target_name} PRIVATE cxx_std_14) 173 | else () 174 | message(FATAL_ERROR "Unsupported compiler -- xeus-robot requires C++14 support!") 175 | endif () 176 | endif () 177 | endmacro() 178 | 179 | # Common macro kernels (xpython and xpython_extension) 180 | macro(xrob_set_kernel_options target_name) 181 | if(XROB_ENABLE_PYPI_WARNING) 182 | message(STATUS "Enabling PyPI warning for target: " ${target_name}) 183 | target_compile_definitions(${target_name} PRIVATE XEUS_ROBOT_PYPI_WARNING) 184 | endif() 185 | 186 | if (XROB_USE_SHARED_XEUS_PYTHON) 187 | target_link_libraries(${target_name} PRIVATE xeus-python) 188 | 189 | if(CMAKE_DL_LIBS) 190 | target_link_libraries(${target_name} PRIVATE ${CMAKE_DL_LIBS} util) 191 | endif() 192 | else () 193 | target_link_libraries(${target_name} PRIVATE xeus-python-static) 194 | endif() 195 | 196 | if (XEUS_PYTHONHOME_RELPATH) 197 | target_compile_definitions(${target_name} PRIVATE XEUS_PYTHONHOME_RELPATH=${XEUS_PYTHONHOME_RELPATH}) 198 | endif() 199 | 200 | target_link_libraries(${target_name} PRIVATE pybind11::pybind11 pybind11_json) 201 | 202 | find_package(Threads) # TODO: add Threads as a dependence of xeus or xeus-static? 203 | target_link_libraries(${target_name} PRIVATE ${CMAKE_THREAD_LIBS_INIT}) 204 | endmacro() 205 | 206 | # xrobot 207 | # ====== 208 | 209 | if (XROB_BUILD_XROBOT_EXECUTABLE) 210 | add_executable(xrobot ${XROBOT_SRC}) 211 | target_link_libraries(xrobot PRIVATE pybind11::embed) 212 | 213 | xrob_set_common_options(xrobot) 214 | xrob_set_kernel_options(xrobot) 215 | endif() 216 | 217 | # xpython_extension 218 | # ================= 219 | 220 | if (XROB_BUILD_XROBOT_EXTENSION) 221 | pybind11_add_module(xrobot_extension ${XROBOT_EXTENSION_SRC}) 222 | 223 | xrob_set_common_options(xrobot_extension) 224 | xrob_set_kernel_options(xrobot_extension) 225 | endif() 226 | 227 | # Tests 228 | # ===== 229 | 230 | if(XROB_DOWNLOAD_GTEST OR XROB_GTEST_SRC_DIR) 231 | set(XROB_BUILD_TESTS ON) 232 | endif() 233 | 234 | if(XROB_BUILD_TESTS) 235 | add_subdirectory(test) 236 | endif() 237 | 238 | # Installation 239 | # ============ 240 | 241 | # Install xrobot 242 | if (XROB_BUILD_XROBOT_EXECUTABLE) 243 | install(TARGETS xrobot 244 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 245 | 246 | # Configuration and data directories for jupyter and xeus-python 247 | set(XJUPYTER_DATA_DIR "share/jupyter" CACHE STRING "Jupyter data directory") 248 | 249 | # Install xrobot Jupyter kernelspec 250 | set(XROB_KERNELSPEC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/share/jupyter/kernels) 251 | install(DIRECTORY ${XROB_KERNELSPEC_DIR} 252 | DESTINATION ${XJUPYTER_DATA_DIR} 253 | PATTERN "*.in" EXCLUDE) 254 | 255 | # Extra path for installing Jupyter kernelspec 256 | if (XEXTRA_JUPYTER_DATA_DIR) 257 | install(DIRECTORY ${XROB_KERNELSPEC_DIR} 258 | DESTINATION ${XEXTRA_JUPYTER_DATA_DIR} 259 | PATTERN "*.in" EXCLUDE) 260 | endif () 261 | endif() 262 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![xeus-robot](images/xeus-robot.svg) 2 | 3 | [![Azure Pipelines](https://dev.azure.com/jupyter-xeus/jupyter-xeus/_apis/build/status/jupyter-xeus.xeus-robot?branchName=master)](https://dev.azure.com/jupyter-xeus/jupyter-xeus/_build/latest?definitionId=3&branchName=master) 4 | [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyter-xeus/xeus-robot/stable?urlpath=/lab/tree/notebooks/xrobot.ipynb) 5 | 6 | `xeus-robot` is a Jupyter kernel for [Robot Framework](https://robotframework.org/) based on the native implementation of the Jupyter protocol [xeus](https://github.com/jupyter-xeus/xeus). 7 | 8 | ## Installation 9 | 10 | ### Using mamba (or conda) 11 | 12 | ```bash 13 | mamba install -c conda-forge xeus-robot 14 | ``` 15 | 16 | ### Using pip 17 | 18 | Depending on the platform, PyPI wheels may be available for xeus-robot. 19 | 20 | ```bash 21 | pip install xeus-robot 22 | ``` 23 | 24 | ### Installing from source 25 | 26 | You can install `xeus-robot` from the sources, you first need to install its dependencies: 27 | 28 | ```bash 29 | mamba install -c conda-forge xeus-python xtl cmake cppzmq nlohmann_json pybind11 pybind11_json robotframework-interpreter ipywidgets jupyterlab_robotmode 30 | ``` 31 | 32 | Then you can compile the sources (replace $CONDA_PREFIX with a custom installation prefix if need be) 33 | 34 | ```bash 35 | mkdir build && cd build 36 | cmake .. -D CMAKE_PREFIX_PATH=$CONDA_PREFIX -D CMAKE_INSTALL_PREFIX=$CONDA_PREFIX -D CMAKE_INSTALL_LIBDIR=lib -D PYTHON_EXECUTABLE=`which python` 37 | make install 38 | ``` 39 | 40 | ### Install the syntax highlighting and widgets for JupyterLab 1 and 2 (It is automatically installed for JupyterLab 3) 41 | 42 | ```bash 43 | jupyter labextension install @marketsquare/jupyterlab_robotmode @jupyter-widgets/jupyterlab-manager 44 | ``` 45 | 46 | ## Dependencies 47 | 48 | ``xeus-robot`` depends on 49 | 50 | - [xeus-python](https://github.com/jupyter-xeus/xeus-python) 51 | - [xtl](https://github.com/xtensor-stack/xtl) 52 | - [pybind11](https://github.com/pybind/pybind11) 53 | - [pybind11_json](https://github.com/pybind/pybind11_json) 54 | - [nlohmann_json](https://github.com/nlohmann/json) 55 | - [robotframework-interpreter](https://github.com/jupyter-xeus/robotframework-interpreter) 56 | - [robotframework-lsp](https://github.com/robocorp/robotframework-lsp) 57 | 58 | 59 | | `xeus-robot`| `xeus-python` | `xtl` | `cppzmq` | `nlohmann_json` | `pybind11` | `pybind11_json` | `robotframework-interpreter` | `robotframework-lsp` | 60 | |-------------|-----------------|-----------------|----------|-----------------|----------------|-------------------|------------------------------|----------------------| 61 | | master | >=0.15.2,<0.16 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.4,<0.8 | >=1.7.0,<2 | 62 | | 0.5.0 | >=0.15.2,<0.16 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.4,<0.8 | >=1.7.0,<2 | 63 | | 0.4.4 | >=0.13.5,<0.14 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.4,<0.8 | >=0.14.0,<0.15.0 | 64 | | 0.4.3 | >=0.13.1,<0.14 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.4,<0.8 | >=0.14.0,<0.15.0 | 65 | | 0.4.2 | >=0.13.1,<0.14 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.4,<0.8 | >=0.14.0,<0.15.0 | 66 | | 0.4.1 | >=0.13.0,<0.14 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.3,<0.8 | >=0.14.0,<0.15.0 | 67 | | 0.4.0 | >=0.13.0,<0.14 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.6.1,<3.0 | >=0.2.6,<0.3 | >=0.7.3,<0.8 | >=0.14.0,<0.15.0 | 68 | | 0.3.8 | >=0.12.4,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.7.3,<0.8 | >=0.14.0,<0.15.0 | 69 | | 0.3.7 | >=0.12.4,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.7.2,<0.8 | >=0.14.0,<0.15.0 | 70 | | 0.3.6 | >=0.12.4,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.7.0,<0.8 | >=0.14.0,<0.15.0 | 71 | | 0.3.5 | >=0.12.4,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.8,<0.7 | >=0.14.0,<0.15.0 | 72 | | 0.3.4 | >=0.12.4,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.6,<0.7 | >=0.14.0,<0.15.0 | 73 | | 0.3.3 | >=0.12.1,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.6,<0.7 | >=0.14.0,<0.15.0 | 74 | | 0.3.2 | >=0.12.1,<0.13 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.6,<0.7 | >=0.4.2,<0.5 | 75 | | 0.3.1 | >=0.11.3,<0.12 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.3,<0.7 | >=0.4.2,<0.5 | 76 | | 0.3.0 | >=0.11.1,<0.12 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.3,<0.7 | >=0.4.2,<0.5 | 77 | | 0.2.2 | >=0.10.2,<0.11 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.2,<0.7 | >=0.4.2,<0.5 | 78 | | 0.2.1 | >=0.10.2,<0.11 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.2,<0.7 | >=0.4.2,<0.5 | 79 | | 0.2.0 | >=0.10.0,<0.11 | >=0.7.0,<0.8 | ~4.7.1 | >=3.6.1,<4.0 | >=2.2.4,<3.0 | >=0.2.6,<0.3 | >=0.6.2,<0.7 | >=0.4.2,<0.5 | 80 | 81 | 82 | ## Examples 83 | 84 | ### Code completion 85 | ![Code completion](images/completion.gif) 86 | 87 | ### Code completion using Selenium selectors 88 | ![Code completion with selenium](images/completion2.gif) 89 | 90 | ### Custom RobotFramework library in Python 91 | ![Custom Python library](images/python.gif) 92 | 93 | ### Debugger support in JupyterLab 3 94 | ![Debugger](images/debugger.gif) 95 | 96 | ### Custom Keywords testing 97 | ![Test Keyword](images/keywords.gif) 98 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - master 3 | 4 | jobs: 5 | - template: ./.azure-pipelines/azure-pipelines-linux.yml 6 | - template: ./.azure-pipelines/azure-pipelines-osx.yml 7 | 8 | -------------------------------------------------------------------------------- /binder-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 12 | 14 | 16 | 19 | 23 | 26 | 27 | 28 | 29 | 30 | 32 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /binder/environment.yml: -------------------------------------------------------------------------------- 1 | name: xeus-robot 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | - xeus-robot=0.5.0 6 | - python=3.9 7 | - robotframework-seleniumlibrary 8 | - robotframework-jupyterlibrary 9 | - ipywidgets>=8,<9 10 | - jupyterlab>=3,<4 11 | -------------------------------------------------------------------------------- /environment-dev.yml: -------------------------------------------------------------------------------- 1 | name: xeus-robot 2 | channels: 3 | - conda-forge 4 | dependencies: 5 | # Build dependencies 6 | - cmake 7 | - cxx-compiler 8 | # Host dependencies 9 | - xeus-python>=0.15.2,<0.16 10 | - robotframework-lsp>=1.7,<2 11 | - xtl 12 | - cppzmq 13 | - nlohmann_json 14 | - pybind11 15 | - pybind11_json 16 | - pip 17 | - robotframework-interpreter>=0.7.4,<0.8 18 | # Test dependencies 19 | - jupyter_kernel_test 20 | -------------------------------------------------------------------------------- /images/completion.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter-xeus/xeus-robot/91f24789d674f33ec18bcfec9bec4efff9fb4048/images/completion.gif -------------------------------------------------------------------------------- /images/completion2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter-xeus/xeus-robot/91f24789d674f33ec18bcfec9bec4efff9fb4048/images/completion2.gif -------------------------------------------------------------------------------- /images/debugger.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter-xeus/xeus-robot/91f24789d674f33ec18bcfec9bec4efff9fb4048/images/debugger.gif -------------------------------------------------------------------------------- /images/keywords.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter-xeus/xeus-robot/91f24789d674f33ec18bcfec9bec4efff9fb4048/images/keywords.gif -------------------------------------------------------------------------------- /images/python.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter-xeus/xeus-robot/91f24789d674f33ec18bcfec9bec4efff9fb4048/images/python.gif -------------------------------------------------------------------------------- /images/xeus-robot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | image/svg+xml 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 | 34 | 36 | 37 | -------------------------------------------------------------------------------- /notebooks/SeleniumScreenshots.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "## Importing" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "The library is imported and used with **SeleniumLibrary** with its name **SeleniumScreenshots**." 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": {}, 21 | "outputs": [], 22 | "source": [ 23 | "*** Settings ***\n", 24 | "\n", 25 | "Library SeleniumLibrary\n", 26 | "Library SeleniumScreenshots" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "*** Variables ***\n", 36 | "\n", 37 | "${BROWSER} firefox" 38 | ] 39 | }, 40 | { 41 | "cell_type": "code", 42 | "execution_count": null, 43 | "metadata": {}, 44 | "outputs": [], 45 | "source": [ 46 | "*** Keywords ***\n", 47 | "\n", 48 | "Open robotframework.org\n", 49 | " Set window size 800 600\n", 50 | " Go to https://robotframework.org" 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "*** Test Cases ***\n", 60 | "\n", 61 | "Open browser\n", 62 | " Open browser about:blank browser=${BROWSER}" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "## Capture and crop page screenshot\n", 70 | "\n", 71 | "Keyword **Capture and crop page screenshot** takes, at first, a screenshot of the current page using **Capture page screenshot** keyword from SeleniumLibrary, and then, crops that screenshot down to the combined bounding box of the elements matching the given locators.\n", 72 | "\n", 73 | "For its argument the keyword requires filename of the resulting screenshot file and one ore more locators." 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": null, 79 | "metadata": {}, 80 | "outputs": [], 81 | "source": [ 82 | "*** Test Cases ***\n", 83 | "\n", 84 | "Capture and crop page screenshot\n", 85 | " Open robotframework.org\n", 86 | " Capture and crop page screenshot\n", 87 | " ... screenshot.png\n", 88 | " ... css:NAV[id='navigation'] > DIV > IMG" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "## Highlight\n", 96 | "\n", 97 | "Keywords **Highlight** and **Clear highlighting** are simple wrappers around **Update element style** keyword to highlight an element by updating its outline style." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "*** Test Cases ***\n", 107 | "\n", 108 | "Highlighting an area\n", 109 | " Open robotframework.org\n", 110 | " Highlight\n", 111 | " ... link:INTRODUCTION # locator (id, css, name or link)\n", 112 | " Capture and crop page screenshot\n", 113 | " ... screenshot.png # filename\n", 114 | " ... link:INTRODUCTION # locator" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": null, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "*** Test Cases ***\n", 124 | "\n", 125 | "Clear highlighting\n", 126 | " Clear highlight link:INTRODUCTION\n", 127 | " Capture and crop page screenshot\n", 128 | " ... screenshot.png\n", 129 | " ... link:INTRODUCTION" 130 | ] 131 | }, 132 | { 133 | "cell_type": "markdown", 134 | "metadata": {}, 135 | "source": [ 136 | "## Update element style\n", 137 | "\n", 138 | "Keyword **Update element style** updates named style with given value for elements matching the given locator." 139 | ] 140 | }, 141 | { 142 | "cell_type": "code", 143 | "execution_count": null, 144 | "metadata": {}, 145 | "outputs": [], 146 | "source": [ 147 | "*** Test Cases ***\n", 148 | "\n", 149 | "Update style\n", 150 | " Open robotframework.org\n", 151 | " Update element style\n", 152 | " ... link:INTRODUCTION # locator (id, css, name or link)\n", 153 | " ... outline # style\n", 154 | " ... dotted yellow 3px # value\n", 155 | " Capture and crop page screenshot\n", 156 | " ... screenshot-1.png\n", 157 | " ... link:INTRODUCTION\n", 158 | " Update element style\n", 159 | " ... link:INTRODUCTION\n", 160 | " ... outline\n", 161 | " ... none\n", 162 | " Capture and crop page screenshot\n", 163 | " ... screenshot-2.png\n", 164 | " ... link:INTRODUCTION" 165 | ] 166 | }, 167 | { 168 | "cell_type": "markdown", 169 | "metadata": {}, 170 | "source": [ 171 | "## Add dot\n", 172 | "\n", 173 | "Keyword **Add dot** adds a dot at the element matching the given locator. By default it aligns to the center of the element, but alignment can be configured with optional **left** and **top** arguments." 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": null, 179 | "metadata": {}, 180 | "outputs": [], 181 | "source": [ 182 | "*** Test Cases ***\n", 183 | "\n", 184 | "Add dot\n", 185 | " Open robotframework.org\n", 186 | " Add dot\n", 187 | " ... link:INTRODUCTION\n", 188 | " ... left=8\n", 189 | " Capture and crop page screenshot\n", 190 | " ... screenshot.png\n", 191 | " ... link:INTRODUCTION" 192 | ] 193 | }, 194 | { 195 | "cell_type": "markdown", 196 | "metadata": {}, 197 | "source": [ 198 | "More importantly, **Add dot** keyword accepts optional **text**-argument, which is intended for annotating sequences." 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "*** Test Cases ***\n", 208 | "\n", 209 | "Add numbered dots dots\n", 210 | " Open robotframework.org\n", 211 | " Add dot\n", 212 | " ... link:INTRODUCTION\n", 213 | " ... text=1\n", 214 | " ... left=8\n", 215 | " Add dot\n", 216 | " ... link:EXAMPLES\n", 217 | " ... text=2\n", 218 | " ... left=8\n", 219 | " Capture and crop page screenshot\n", 220 | " ... screenshot.png\n", 221 | " ... link:INTRODUCTION\n", 222 | " ... link:EXAMPLES" 223 | ] 224 | }, 225 | { 226 | "cell_type": "markdown", 227 | "metadata": {}, 228 | "source": [ 229 | "## Remove element\n", 230 | "\n", 231 | "All **SeleniumScreenshots** keywords that add new elements onto the current page return the id of the created element. That id can be saved into a variable and used e.g. with **Update element style**-keyword or, as shown below, with **Remove element** keyword for removing the annotation." 232 | ] 233 | }, 234 | { 235 | "cell_type": "code", 236 | "execution_count": null, 237 | "metadata": {}, 238 | "outputs": [], 239 | "source": [ 240 | "*** Test Cases ***\n", 241 | "\n", 242 | "Add and remove dot\n", 243 | " Open robotframework.org\n", 244 | " ${dot}= Add dot\n", 245 | " ... link:INTRODUCTION\n", 246 | " ... left=8 background=red\n", 247 | " Capture and crop page screenshot\n", 248 | " ... screenshot-1.png\n", 249 | " ... link:INTRODUCTION\n", 250 | " Remove element ${dot}\n", 251 | " Capture and crop page screenshot\n", 252 | " ... screenshot-2.png\n", 253 | " ... link:INTRODUCTION" 254 | ] 255 | }, 256 | { 257 | "cell_type": "markdown", 258 | "metadata": {}, 259 | "source": [ 260 | "Remove element has plural version **Remove elements** which accepts multiple locators." 261 | ] 262 | }, 263 | { 264 | "cell_type": "markdown", 265 | "metadata": {}, 266 | "source": [ 267 | "## Add note" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": {}, 273 | "source": [ 274 | "Keyword **Add note** adds a yellow sticky onto the current page. Its main configuration arguments beyond the locator are **text** and **position** (accepting *center*, *top*, *right*, *bottom* and *left*). Multiline notes should define their **width**." 275 | ] 276 | }, 277 | { 278 | "cell_type": "code", 279 | "execution_count": null, 280 | "metadata": {}, 281 | "outputs": [], 282 | "source": [ 283 | "*** Test Cases ***\n", 284 | "\n", 285 | "Add note\n", 286 | " Open robotframework.org\n", 287 | " ${note} = Add note \n", 288 | " ... css:NAV[id='navigation'] > DIV > IMG\n", 289 | " ... text=I am a robot. With opinions.\n", 290 | " ... width=160\n", 291 | " ... position=right\n", 292 | " Capture and crop page screenshot\n", 293 | " ... screenshot.png\n", 294 | " ... css:NAV[id='navigation'] > DIV > IMG\n", 295 | " ... ${note}" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "## Add pointy note\n", 303 | "\n", 304 | "Keyword **Add pointy note** is similar to **Add note** with the difference that its notes come with a pointer triangle targeting the element." 305 | ] 306 | }, 307 | { 308 | "cell_type": "code", 309 | "execution_count": null, 310 | "metadata": {}, 311 | "outputs": [], 312 | "source": [ 313 | "*** Test Cases ***\n", 314 | "\n", 315 | "Add pointy note\n", 316 | " Open robotframework.org\n", 317 | " ${note} = Add pointy note\n", 318 | " ... link:INTRODUCTION\n", 319 | " ... text=Start here.\n", 320 | " ... position=right\n", 321 | " Capture and crop page screenshot\n", 322 | " ... screenshot.png\n", 323 | " ... link:INTRODUCTION\n", 324 | " ... ${note}" 325 | ] 326 | }, 327 | { 328 | "cell_type": "markdown", 329 | "metadata": {}, 330 | "source": [ 331 | "## Notes" 332 | ] 333 | }, 334 | { 335 | "cell_type": "markdown", 336 | "metadata": {}, 337 | "source": [ 338 | "This introduction has been created as executable [Jupyter notebook](https://jupyter.org/) with [robotkernel](https://pypi.org/project/robotkernel/). Interactive version of this notebook is available for download [downloaded from GitHub](https://github.com/datakurre/robotframework-seleniumscreenshots/blob/master/docs/Introduction%20to%20SeleniumScreenshots.ipynb).\n", 339 | "\n", 340 | "When this notebook is exported into ``robot`` file to be run with ``robot`` (or runned as such with ``nbrobot`` from robotkernel), test browser windows must be explicitly closed, requiring the following setting:" 341 | ] 342 | }, 343 | { 344 | "cell_type": "code", 345 | "execution_count": null, 346 | "metadata": {}, 347 | "outputs": [], 348 | "source": [ 349 | "*** Settings ***\n", 350 | "\n", 351 | "Suite Teardown Close all browsers" 352 | ] 353 | } 354 | ], 355 | "metadata": { 356 | "kernelspec": { 357 | "display_name": "RobotFramework (XRobot)", 358 | "language": "robotframework", 359 | "name": "xrobot" 360 | }, 361 | "language_info": { 362 | "codemirror_mode": "robotframework", 363 | "file_extension": ".robot", 364 | "mimetype": "text/x-robotframework", 365 | "name": "robotframework", 366 | "pygments_lexer": "robotframework", 367 | "version": "3.2" 368 | }, 369 | "widgets": { 370 | "application/vnd.jupyter.widget-state+json": { 371 | "state": { 372 | "053cfe2a753b4acf995af8392d9e4047": { 373 | "model_module": "@jupyter-widgets/base", 374 | "model_module_version": "1.2.0", 375 | "model_name": "LayoutModel", 376 | "state": { 377 | "display": "flex", 378 | "flex_flow": "row", 379 | "justify_content": "flex-start" 380 | } 381 | }, 382 | "40bfc0dc8625466ea41ea43260ff1f23": { 383 | "model_module": "@jupyter-widgets/output", 384 | "model_module_version": "1.0.0", 385 | "model_name": "OutputModel", 386 | "state": { 387 | "layout": "IPY_MODEL_aa4c1e8c7ab44136a33edfbf37640343" 388 | } 389 | }, 390 | "7e0028dd66ef4d1e8adfa2851b9f26e2": { 391 | "model_module": "@jupyter-widgets/base", 392 | "model_module_version": "1.2.0", 393 | "model_name": "LayoutModel", 394 | "state": {} 395 | }, 396 | "aa3bf9afb26f4f0a89855d17517004b8": { 397 | "model_module": "@jupyter-widgets/controls", 398 | "model_module_version": "1.5.0", 399 | "model_name": "BoxModel", 400 | "state": { 401 | "children": [ 402 | "IPY_MODEL_cae2a560299b42a9845ef0c963a94980" 403 | ], 404 | "layout": "IPY_MODEL_053cfe2a753b4acf995af8392d9e4047" 405 | } 406 | }, 407 | "aa4c1e8c7ab44136a33edfbf37640343": { 408 | "model_module": "@jupyter-widgets/base", 409 | "model_module_version": "1.2.0", 410 | "model_name": "LayoutModel", 411 | "state": {} 412 | }, 413 | "cae2a560299b42a9845ef0c963a94980": { 414 | "model_module": "@jupyter-widgets/controls", 415 | "model_module_version": "1.5.0", 416 | "model_name": "ButtonModel", 417 | "state": { 418 | "description": "Open robotframework.org", 419 | "layout": "IPY_MODEL_7e0028dd66ef4d1e8adfa2851b9f26e2", 420 | "style": "IPY_MODEL_dab840071d1649698055fcf3e14d8cb5" 421 | } 422 | }, 423 | "dab840071d1649698055fcf3e14d8cb5": { 424 | "model_module": "@jupyter-widgets/controls", 425 | "model_module_version": "1.5.0", 426 | "model_name": "ButtonStyleModel", 427 | "state": {} 428 | } 429 | }, 430 | "version_major": 2, 431 | "version_minor": 0 432 | } 433 | } 434 | }, 435 | "nbformat": 4, 436 | "nbformat_minor": 4 437 | } 438 | -------------------------------------------------------------------------------- /notebooks/xrobot.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "*** Settings ***\n", 10 | "\n", 11 | "Library String\n", 12 | "\n", 13 | "*** Variables ***\n", 14 | "\n", 15 | "${VARNAME} Coucou" 16 | ] 17 | }, 18 | { 19 | "cell_type": "code", 20 | "execution_count": null, 21 | "metadata": {}, 22 | "outputs": [], 23 | "source": [ 24 | "*** Variables ***\n", 25 | "\n", 26 | "${VARNAME} Hello World" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": {}, 33 | "outputs": [], 34 | "source": [ 35 | "*** Test Cases ***\n", 36 | "\n", 37 | "Test variable name\n", 38 | " ${LOWER} = Convert To Lower Case ${VARNAME}\n", 39 | " Should Be Lowercase ${LOWER}\n", 40 | " Should start with ${LOWER} hello\n", 41 | "\n", 42 | "Test variable name should fail\n", 43 | " Should start with ${VARNAME} Coucou" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": null, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "*** Test Cases ***\n", 53 | "\n", 54 | "Test variable name should fail\n", 55 | " Should start with ${VARNAME} Coucou" 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": null, 61 | "metadata": {}, 62 | "outputs": [], 63 | "source": [] 64 | } 65 | ], 66 | "metadata": { 67 | "kernelspec": { 68 | "display_name": "Robot Framework (xeus-robot)", 69 | "language": "robotframework", 70 | "name": "xrobot" 71 | }, 72 | "language_info": { 73 | "codemirror_mode": "robotframework", 74 | "file_extension": ".robot", 75 | "mimetype": "text/x-robotframework", 76 | "name": "robotframework", 77 | "pygments_lexer": "robotframework", 78 | "version": "3.2" 79 | } 80 | }, 81 | "nbformat": 4, 82 | "nbformat_minor": 4 83 | } 84 | -------------------------------------------------------------------------------- /share/jupyter/kernels/xrobot/kernel.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | "use strict"; 4 | 5 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 6 | // Distributed under an MIT license: http://codemirror.net/LICENSE 7 | 8 | CodeMirror.defineSimpleMode = function(name, states) { 9 | CodeMirror.defineMode(name, function(config) { 10 | return CodeMirror.simpleMode(config, states); 11 | }); 12 | }; 13 | 14 | CodeMirror.simpleMode = function(config, states) { 15 | ensureState(states, "start"); 16 | var states_ = {}, meta = states.meta || {}, hasIndentation = false; 17 | for (var state in states) if (state != meta && states.hasOwnProperty(state)) { 18 | var list = states_[state] = [], orig = states[state]; 19 | for (var i = 0; i < orig.length; i++) { 20 | var data = orig[i]; 21 | list.push(new Rule(data, states)); 22 | if (data.indent || data.dedent) hasIndentation = true; 23 | } 24 | } 25 | var mode = { 26 | startState: function() { 27 | return {state: "start", pending: null, 28 | local: null, localState: null, 29 | indent: hasIndentation ? [] : null}; 30 | }, 31 | copyState: function(state) { 32 | var s = {state: state.state, pending: state.pending, 33 | local: state.local, localState: null, 34 | indent: state.indent && state.indent.slice(0)}; 35 | if (state.localState) 36 | s.localState = CodeMirror.copyState(state.local.mode, state.localState); 37 | if (state.stack) 38 | s.stack = state.stack.slice(0); 39 | for (var pers = state.persistentStates; pers; pers = pers.next) 40 | s.persistentStates = {mode: pers.mode, 41 | spec: pers.spec, 42 | state: pers.state == state.localState ? s.localState : CodeMirror.copyState(pers.mode, pers.state), 43 | next: s.persistentStates}; 44 | return s; 45 | }, 46 | token: tokenFunction(states_, config), 47 | innerMode: function(state) { return state.local && {mode: state.local.mode, state: state.localState}; }, 48 | indent: indentFunction(states_, meta) 49 | }; 50 | if (meta) for (var prop in meta) if (meta.hasOwnProperty(prop)) 51 | mode[prop] = meta[prop]; 52 | return mode; 53 | }; 54 | 55 | function ensureState(states, name) { 56 | if (!states.hasOwnProperty(name)) 57 | throw new Error("Undefined state " + name + " in simple mode"); 58 | } 59 | 60 | function toRegex(val, caret) { 61 | if (!val) return /(?:)/; 62 | var flags = ""; 63 | if (val instanceof RegExp) { 64 | if (val.ignoreCase) flags = "i"; 65 | val = val.source; 66 | } else { 67 | val = String(val); 68 | } 69 | return new RegExp((caret === false ? "" : "^") + "(?:" + val + ")", flags); 70 | } 71 | 72 | function asToken(val) { 73 | if (!val) return null; 74 | if (val.apply) return val 75 | if (typeof val == "string") return val.replace(/\./g, " "); 76 | var result = []; 77 | for (var i = 0; i < val.length; i++) 78 | result.push(val[i] && val[i].replace(/\./g, " ")); 79 | return result; 80 | } 81 | 82 | function Rule(data, states) { 83 | if (data.next || data.push) ensureState(states, data.next || data.push); 84 | this.regex = toRegex(data.regex); 85 | this.token = asToken(data.token); 86 | this.data = data; 87 | } 88 | 89 | function tokenFunction(states, config) { 90 | return function(stream, state) { 91 | if (state.pending) { 92 | var pend = state.pending.shift(); 93 | if (state.pending.length == 0) state.pending = null; 94 | stream.pos += pend.text.length; 95 | return pend.token; 96 | } 97 | 98 | if (state.local) { 99 | if (state.local.end && stream.match(state.local.end)) { 100 | var tok = state.local.endToken || null; 101 | state.local = state.localState = null; 102 | return tok; 103 | } else { 104 | var tok = state.local.mode.token(stream, state.localState), m; 105 | if (state.local.endScan && (m = state.local.endScan.exec(stream.current()))) 106 | stream.pos = stream.start + m.index; 107 | return tok; 108 | } 109 | } 110 | 111 | var curState = states[state.state]; 112 | for (var i = 0; i < curState.length; i++) { 113 | var rule = curState[i]; 114 | var matches = (!rule.data.sol || stream.sol()) && stream.match(rule.regex); 115 | if (matches) { 116 | if (rule.data.next) { 117 | state.state = rule.data.next; 118 | } else if (rule.data.push) { 119 | (state.stack || (state.stack = [])).push(state.state); 120 | state.state = rule.data.push; 121 | } else if (rule.data.pop && state.stack && state.stack.length) { 122 | state.state = state.stack.pop(); 123 | } 124 | 125 | if (rule.data.mode) 126 | enterLocalMode(config, state, rule.data.mode, rule.token); 127 | if (rule.data.indent) 128 | state.indent.push(stream.indentation() + config.indentUnit); 129 | if (rule.data.dedent) 130 | state.indent.pop(); 131 | var token = rule.token 132 | if (token && token.apply) token = token(matches) 133 | if (matches.length > 2 && rule.token && typeof rule.token != "string") { 134 | state.pending = []; 135 | for (var j = 2; j < matches.length; j++) 136 | if (matches[j]) 137 | state.pending.push({text: matches[j], token: rule.token[j - 1]}); 138 | stream.backUp(matches[0].length - (matches[1] ? matches[1].length : 0)); 139 | return token[0]; 140 | } else if (token && token.join) { 141 | return token[0]; 142 | } else { 143 | return token; 144 | } 145 | } 146 | } 147 | stream.next(); 148 | return null; 149 | }; 150 | } 151 | 152 | function cmp(a, b) { 153 | if (a === b) return true; 154 | if (!a || typeof a != "object" || !b || typeof b != "object") return false; 155 | var props = 0; 156 | for (var prop in a) if (a.hasOwnProperty(prop)) { 157 | if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) return false; 158 | props++; 159 | } 160 | for (var prop in b) if (b.hasOwnProperty(prop)) props--; 161 | return props == 0; 162 | } 163 | 164 | function enterLocalMode(config, state, spec, token) { 165 | var pers; 166 | if (spec.persistent) for (var p = state.persistentStates; p && !pers; p = p.next) 167 | if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode == p.mode) pers = p; 168 | var mode = pers ? pers.mode : spec.mode || CodeMirror.getMode(config, spec.spec); 169 | var lState = pers ? pers.state : CodeMirror.startState(mode); 170 | if (spec.persistent && !pers) 171 | state.persistentStates = {mode: mode, spec: spec.spec, state: lState, next: state.persistentStates}; 172 | 173 | state.localState = lState; 174 | state.local = {mode: mode, 175 | end: spec.end && toRegex(spec.end), 176 | endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false), 177 | endToken: token && token.join ? token[token.length - 1] : token}; 178 | } 179 | 180 | function indexOf(val, arr) { 181 | for (var i = 0; i < arr.length; i++) if (arr[i] === val) return true; 182 | } 183 | 184 | function indentFunction(states, meta) { 185 | return function(state, textAfter, line) { 186 | if (state.local && state.local.mode.indent) 187 | return state.local.mode.indent(state.localState, textAfter, line); 188 | if (state.indent == null || state.local || meta.dontIndentStates && indexOf(state.state, meta.dontIndentStates) > -1) 189 | return CodeMirror.Pass; 190 | 191 | var pos = state.indent.length - 1, rules = states[state.state]; 192 | scan: for (;;) { 193 | for (var i = 0; i < rules.length; i++) { 194 | var rule = rules[i]; 195 | if (rule.data.dedent && rule.data.dedentIfLineStart !== false) { 196 | var m = rule.regex.exec(textAfter); 197 | if (m && m[0]) { 198 | pos--; 199 | if (rule.next || rule.push) rules = states[rule.next || rule.push]; 200 | textAfter = textAfter.slice(m[0].length); 201 | continue scan; 202 | } 203 | } 204 | } 205 | break; 206 | } 207 | return pos < 0 ? 0 : state.indent[pos]; 208 | }; 209 | } 210 | 211 | /* 212 | Copyright (c) 2018 Georgia Tech Research Corporation 213 | Copyright (c) 2019 Asko Soukka 214 | Distributed under the terms of the BSD-3-Clause License 215 | */ 216 | var __assign = (this && this.__assign) || function () { 217 | __assign = Object.assign || function(t) { 218 | for (var s, i = 1, n = arguments.length; i < n; i++) { 219 | s = arguments[i]; 220 | for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) 221 | t[p] = s[p]; 222 | } 223 | return t; 224 | }; 225 | return __assign.apply(this, arguments); 226 | }; 227 | /* 228 | An implementation of syntax highlighting for robot framework 3.1. 229 | 230 | http://robotframework.org/robotframework/3.1/RobotFrameworkUserGuide.html 231 | 232 | When necessary, the source code is consulted and ultimately trusted: 233 | 234 | https://github.com/robotframework/robotframework 235 | */ 236 | // tslint:disable-next-line 237 | /// 238 | /** the tokens we use */ 239 | var TT = {}; 240 | (function (TT) { 241 | TT["AM"] = "atom"; 242 | TT["AT"] = "attribute"; 243 | TT["BE"] = "builtin.em"; 244 | TT["BI"] = "builtin"; 245 | TT["BK"] = "bracket"; 246 | TT["CM"] = "comment"; 247 | TT["DF"] = "def"; 248 | TT["HL"] = "header"; 249 | TT["KW"] = "keyword"; 250 | TT["MT"] = "meta"; 251 | TT["NB"] = "number"; 252 | TT["OP"] = "operator"; 253 | TT["PC"] = "punctuation"; 254 | TT["PR"] = "property"; 255 | TT["SE"] = "string.em"; 256 | TT["SH"] = "string.header"; 257 | TT["SS"] = "string.strong"; 258 | TT["SSE"] = "string.strong.em"; 259 | TT["S2"] = "string-2"; 260 | TT["ST"] = "string"; 261 | TT["TG"] = "tag"; 262 | TT["V2"] = "variable-2"; 263 | })(TT); 264 | function LINK(token) { 265 | return (token + '.link'); 266 | } 267 | /** helper function for compactly representing a rule */ 268 | function r(regex, token, opt) { 269 | return __assign({ regex: regex, token: token }, opt); 270 | } 271 | /** Possible Robot Framework table names. Group count is important. */ 272 | var TABLE_NAMES = { 273 | keywords: /(\|\s)?(\*+ *)(user keywords?|keywords?)( *\**)/i, 274 | settings: /(\|\s)?(\*+ *)(settings?)( *\**)/i, 275 | test_cases: /(\|\s)?(\*+ *)(tasks?|test cases?)( *\**)/i, 276 | variables: /(\|\s)?(\*+ *)(variables?)( *\**)/i 277 | }; 278 | /** Enumerate the possible rules */ 279 | var RULES_TABLE = Object.keys(TABLE_NAMES).map(function (next) { 280 | return r(TABLE_NAMES[next], [TT.BK, TT.HL, TT.HL, TT.HL], { 281 | next: next, 282 | sol: true 283 | }); 284 | }); 285 | var RULE_COMMENT_POP = r(/#.*$/, TT.CM, { pop: true }); 286 | /** Valid python operators */ 287 | var VAR_OP = /[*\-+\\%&|=> 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include "xeus/xeus_context.hpp" 19 | #include "xeus/xkernel.hpp" 20 | #include "xeus/xkernel_configuration.hpp" 21 | 22 | #include "xeus-zmq/xserver_shell_main.hpp" 23 | 24 | #include "pybind11/embed.h" 25 | #include "pybind11/pybind11.h" 26 | 27 | #include "xeus-python/xpaths.hpp" 28 | #include "xeus-python/xutils.hpp" 29 | #include "xeus_robot_config.hpp" 30 | 31 | #include "xinterpreter.hpp" 32 | #include "xdebugger.hpp" 33 | 34 | 35 | int main(int argc, char* argv[]) 36 | { 37 | if (xpyt::should_print_version(argc, argv)) 38 | { 39 | std::clog << "xrobot " << XROB_VERSION << std::endl; 40 | return 0; 41 | } 42 | 43 | // If we are called from the Jupyter launcher, silence all logging. This 44 | // is important for a JupyterHub configured with cleanup_servers = False: 45 | // Upon restart, spawned single-user servers keep running but without the 46 | // std* streams. When a user then tries to start a new kernel, xpython 47 | // will get a SIGPIPE and exit. 48 | if (std::getenv("JPY_PARENT_PID") != NULL) 49 | { 50 | std::clog.setstate(std::ios_base::failbit); 51 | } 52 | 53 | // Registering SIGSEGV handler 54 | #ifdef __GNUC__ 55 | std::clog << "registering handler for SIGSEGV" << std::endl; 56 | signal(SIGSEGV, xpyt::sigsegv_handler); 57 | 58 | // Registering SIGINT and SIGKILL handlers 59 | signal(SIGKILL, xpyt::sigkill_handler); 60 | #endif 61 | signal(SIGINT, xpyt::sigkill_handler); 62 | 63 | // Setting Program Name 64 | static const std::string executable(xpyt::get_python_path()); 65 | static const std::wstring wexecutable(executable.cbegin(), executable.cend()); 66 | 67 | // On windows, sys.executable is not properly set with Py_SetProgramName 68 | // Cf. https://bugs.python.org/issue34725 69 | // A private undocumented API was added as a workaround in Python 3.7.2. 70 | // _Py_SetProgramFullPath(const_cast(wexecutable.c_str())); 71 | Py_SetProgramName(const_cast(wexecutable.c_str())); 72 | 73 | // Setting PYTHONHOME 74 | xpyt::set_pythonhome(); 75 | xpyt::print_pythonhome(); 76 | 77 | // Instanciating the Python interpreter 78 | py::scoped_interpreter guard; 79 | 80 | // Setting argv 81 | wchar_t** argw = new wchar_t*[size_t(argc)]; 82 | for(auto i = 0; i < argc; ++i) 83 | { 84 | argw[i] = Py_DecodeLocale(argv[i], nullptr); 85 | } 86 | PySys_SetArgvEx(argc, argw, 0); 87 | for(auto i = 0; i < argc; ++i) 88 | { 89 | PyMem_RawFree(argw[i]); 90 | } 91 | delete[] argw; 92 | 93 | // Instantiating the xeus xinterpreter 94 | using interpreter_ptr = std::unique_ptr; 95 | interpreter_ptr interpreter = interpreter_ptr(new xrob::interpreter()); 96 | 97 | using history_manager_ptr = std::unique_ptr; 98 | history_manager_ptr hist = xeus::make_in_memory_history_manager(); 99 | 100 | nl::json debugger_config = nl::json::object(); 101 | 102 | std::string connection_filename = xpyt::extract_parameter("-f", argc, argv); 103 | 104 | auto context = xeus::make_context(); 105 | 106 | if (!connection_filename.empty()) 107 | { 108 | xeus::xconfiguration config = xeus::load_configuration(connection_filename); 109 | 110 | xeus::xkernel kernel(config, 111 | xeus::get_user_name(), 112 | std::move(context), 113 | std::move(interpreter), 114 | xeus::make_xserver_shell_main, 115 | std::move(hist), 116 | xeus::make_console_logger(xeus::xlogger::msg_type, 117 | xeus::make_file_logger(xeus::xlogger::content, "xeus.log")), 118 | xrob::make_robot_debugger, 119 | debugger_config); 120 | 121 | std::clog << 122 | "Starting xeus-robot kernel...\n\n" 123 | "If you want to connect to this kernel from an other client, you can use" 124 | " the " + connection_filename + " file." 125 | << std::endl; 126 | 127 | kernel.start(); 128 | } 129 | else 130 | { 131 | xeus::xkernel kernel(xeus::get_user_name(), 132 | std::move(context), 133 | std::move(interpreter), 134 | xeus::make_xserver_shell_main, 135 | std::move(hist), 136 | nullptr, 137 | xrob::make_robot_debugger, 138 | debugger_config); 139 | 140 | const auto& config = kernel.get_config(); 141 | std::clog << 142 | "Starting xeus-robot kernel...\n\n" 143 | "If you want to connect to this kernel from an other client, just copy" 144 | " and paste the following content inside of a `kernel.json` file. And then run for example:\n\n" 145 | "# jupyter console --existing kernel.json\n\n" 146 | "kernel.json\n```\n{\n" 147 | " \"transport\": \"" + config.m_transport + "\",\n" 148 | " \"ip\": \"" + config.m_ip + "\",\n" 149 | " \"control_port\": " + config.m_control_port + ",\n" 150 | " \"shell_port\": " + config.m_shell_port + ",\n" 151 | " \"stdin_port\": " + config.m_stdin_port + ",\n" 152 | " \"iopub_port\": " + config.m_iopub_port + ",\n" 153 | " \"hb_port\": " + config.m_hb_port + ",\n" 154 | " \"signature_scheme\": \"" + config.m_signature_scheme + "\",\n" 155 | " \"key\": \"" + config.m_key + "\"\n" 156 | "}\n```" 157 | << std::endl; 158 | 159 | kernel.start(); 160 | } 161 | 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /src/xdebugger.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | // This must be included BEFORE pybind 18 | // otherwise it fails to build on Windows 19 | // because of the redefinition of snprintf 20 | #include "nlohmann/json.hpp" 21 | 22 | #include "pybind11_json/pybind11_json.hpp" 23 | 24 | #include "pybind11/pybind11.h" 25 | #include "pybind11/stl.h" 26 | 27 | #include "xeus/xinterpreter.hpp" 28 | #include "xeus/xsystem.hpp" 29 | 30 | #include "xeus-zmq/xmiddleware.hpp" 31 | 32 | #include "xdebugger.hpp" 33 | #include "xrobodebug_client.hpp" 34 | #include "xinternal_utils.hpp" 35 | 36 | namespace nl = nlohmann; 37 | namespace py = pybind11; 38 | 39 | using namespace pybind11::literals; 40 | using namespace std::placeholders; 41 | 42 | namespace xrob 43 | { 44 | debugger::debugger(zmq::context_t& context, 45 | const xeus::xconfiguration& config, 46 | const std::string& user_name, 47 | const std::string& session_id, 48 | const nl::json& debugger_config) 49 | : xdebugger_base(context) 50 | , p_robodebug_client(new xrobodebug_client(context, 51 | config, 52 | xeus::get_socket_linger(), 53 | xdap_tcp_configuration(xeus::dap_tcp_type::server, 54 | xeus::dap_init_type::sequential, 55 | user_name, 56 | session_id), 57 | get_event_callback())) 58 | 59 | , m_robodebug_host("127.0.0.1") 60 | , m_robodebug_port("") 61 | , m_debugger_config(debugger_config) 62 | { 63 | register_request_handler("inspectVariables", std::bind(&debugger::inspect_variables_request, this, _1), false); 64 | m_robodebug_port = xeus::find_free_port(100, 5678, 5900); 65 | } 66 | 67 | debugger::~debugger() 68 | { 69 | delete p_robodebug_client; 70 | p_robodebug_client = nullptr; 71 | } 72 | 73 | nl::json debugger::inspect_variables_request(const nl::json& message) 74 | { 75 | py::gil_scoped_acquire acquire; 76 | py::object variables = py::globals(); 77 | 78 | nl::json json_vars = nl::json::array(); 79 | for (const py::handle& key : variables) 80 | { 81 | nl::json json_var = nl::json::object(); 82 | json_var["name"] = py::str(key); 83 | json_var["variablesReference"] = 0; 84 | try 85 | { 86 | json_var["value"] = variables[key]; 87 | } 88 | catch(std::exception&) 89 | { 90 | json_var["value"] = py::repr(variables[key]); 91 | } 92 | json_vars.push_back(json_var); 93 | } 94 | 95 | nl::json reply = { 96 | {"type", "response"}, 97 | {"request_seq", message["seq"]}, 98 | {"success", true}, 99 | {"command", message["command"]}, 100 | {"body", { 101 | {"variables", json_vars} 102 | }} 103 | }; 104 | 105 | return reply; 106 | } 107 | 108 | bool debugger::start(zmq::socket_t& header_socket, zmq::socket_t& request_socket) 109 | { 110 | std::string temp_dir = xeus::get_temp_directory_path(); 111 | std::string log_dir = temp_dir + "/" + "xpython_debug_logs_" + std::to_string(xeus::get_current_pid()); 112 | 113 | std::string controller_end_point = xeus::get_controller_end_point("debugger"); 114 | std::string controller_header_end_point = xeus::get_controller_end_point("debugger_header"); 115 | std::string publisher_end_point = xeus::get_publisher_end_point(); 116 | 117 | request_socket.bind(controller_end_point); 118 | header_socket.bind(controller_header_end_point); 119 | 120 | std::string robodebug_end_point = "tcp://" + m_robodebug_host + ':' + m_robodebug_port; 121 | std::thread client(&xrobodebug_client::start_debugger, 122 | p_robodebug_client, 123 | robodebug_end_point, 124 | publisher_end_point, 125 | controller_end_point, 126 | controller_header_end_point); 127 | client.detach(); 128 | 129 | 130 | std::string tmp_folder = get_tmp_prefix(); 131 | xeus::create_directory(tmp_folder); 132 | 133 | xeus::create_directory(log_dir); 134 | 135 | std::string var_py = "robot_port = " + m_robodebug_port 136 | + "\nlog_file=\'" + log_dir + "/xrobot.log\'\n"; 137 | std::string init_logger_py = R"( 138 | import robotframework_ls 139 | robotframework_ls.import_robocorp_ls_core() 140 | from robocorp_ls_core.robotframework_log import configure_logger, _log_config 141 | configure_logger("robot", 3, log_file) 142 | )"; 143 | std::string init_debugger_py = R"( 144 | from robotframework_debug_adapter.run_robot__main__ import connect, _RobotTargetComm 145 | s = connect(int(robot_port)) 146 | processor = _RobotTargetComm(s, debug=True) 147 | processor.start_communication_threads(False) 148 | )"; 149 | std::string init_listener_py = R"( 150 | from robotframework_debug_adapter.listeners import DebugListener, DebugListenerV2 151 | debug_listener = DebugListener() 152 | debug_listenerv2 = DebugListenerV2() 153 | )"; 154 | 155 | std::string code = var_py + init_logger_py + init_debugger_py + init_listener_py; 156 | 157 | nl::json json_code; 158 | json_code["port"] = m_robodebug_port; 159 | json_code["code"] = code; 160 | nl::json rep = xdebugger::get_control_messenger().send_to_shell(json_code); 161 | std::string status = rep["status"].get(); 162 | if(status != "ok") 163 | { 164 | std::string ename = rep["ename"].get(); 165 | std::string evalue = rep["evalue"].get(); 166 | std::vector traceback = rep["traceback"].get>(); 167 | std::clog << "Exception raised when trying to import ptvsd" << std::endl; 168 | for(std::size_t i = 0; i < traceback.size(); ++i) 169 | { 170 | std::clog << traceback[i] << std::endl; 171 | } 172 | std::clog << ename << " - " << evalue << std::endl; 173 | } 174 | 175 | request_socket.send(zmq::message_t("REQ", 3), zmq::send_flags::none); 176 | zmq::message_t ack; 177 | (void)request_socket.recv(ack); 178 | 179 | return true; 180 | } 181 | 182 | void debugger::stop(zmq::socket_t& header_socket, zmq::socket_t& request_socket) 183 | { 184 | std::string controller_end_point = xeus::get_controller_end_point("debugger"); 185 | std::string controller_header_end_point = xeus::get_controller_end_point("debugger_header"); 186 | request_socket.unbind(controller_end_point); 187 | header_socket.unbind(controller_header_end_point); 188 | } 189 | 190 | xeus::xdebugger_info debugger::get_debugger_info() const 191 | { 192 | return xeus::xdebugger_info(xeus::get_tmp_hash_seed(), 193 | get_tmp_prefix(), 194 | get_tmp_suffix()); 195 | } 196 | 197 | std::string debugger::get_cell_temporary_file(const std::string& code) const 198 | { 199 | return get_cell_tmp_file(code); 200 | } 201 | 202 | std::unique_ptr make_robot_debugger(xeus::xcontext& context, 203 | const xeus::xconfiguration& config, 204 | const std::string& user_name, 205 | const std::string& session_id, 206 | const nl::json& debugger_config) 207 | { 208 | return std::unique_ptr(new debugger(context.get_wrapped_context(), 209 | config, user_name, session_id, debugger_config)); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/xdebugger.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #ifndef XROB_DEBUGGER_HPP 12 | #define XROB_DEBUGGER_HPP 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include "zmq.hpp" 19 | 20 | #include "nlohmann/json.hpp" 21 | 22 | #include "xeus_robot_config.hpp" 23 | #include "xeus/xeus_context.hpp" 24 | 25 | #include "xeus-zmq/xdebugger_base.hpp" 26 | 27 | namespace xrob 28 | { 29 | 30 | class xrobodebug_client; 31 | 32 | class debugger : public xeus::xdebugger_base 33 | { 34 | public: 35 | 36 | debugger(zmq::context_t& context, 37 | const xeus::xconfiguration& config, 38 | const std::string& user_name, 39 | const std::string& session_id, 40 | const nl::json& debugger_config); 41 | 42 | virtual ~debugger(); 43 | 44 | private: 45 | 46 | nl::json inspect_variables_request(const nl::json& message); 47 | 48 | bool start(zmq::socket_t& header_socket, 49 | zmq::socket_t& request_socket) override; 50 | void stop(zmq::socket_t& header_socket, 51 | zmq::socket_t& request_socket) override; 52 | xeus::xdebugger_info get_debugger_info() const override; 53 | std::string get_cell_temporary_file(const std::string& code) const override; 54 | 55 | xrobodebug_client* p_robodebug_client; 56 | std::string m_robodebug_host; 57 | std::string m_robodebug_port; 58 | nl::json m_debugger_config; 59 | }; 60 | 61 | std::unique_ptr make_robot_debugger(xeus::xcontext& context, 62 | const xeus::xconfiguration& config, 63 | const std::string& user_name, 64 | const std::string& session_id, 65 | const nl::json& debugger_config); 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/xeus_robot_config.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2020, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2020, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #ifndef XROB_CONFIG_HPP 12 | #define XROB_CONFIG_HPP 13 | 14 | // Project version 15 | #define XROB_VERSION_MAJOR 0 16 | #define XROB_VERSION_MINOR 5 17 | #define XROB_VERSION_PATCH 0 18 | 19 | // Composing the version string from major, minor and patch 20 | #define XROB_CONCATENATE(A, B) XROB_CONCATENATE_IMPL(A, B) 21 | #define XROB_CONCATENATE_IMPL(A, B) A##B 22 | #define XROB_STRINGIFY(a) XROB_STRINGIFY_IMPL(a) 23 | #define XROB_STRINGIFY_IMPL(a) #a 24 | 25 | #define XROB_VERSION XROB_STRINGIFY(XROB_CONCATENATE(XROB_VERSION_MAJOR, \ 26 | XROB_CONCATENATE(.,XROB_CONCATENATE(XROB_VERSION_MINOR, \ 27 | XROB_CONCATENATE(.,XROB_VERSION_PATCH))))) 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /src/xinternal_utils.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include "xeus/xsystem.hpp" 12 | #include "xinternal_utils.hpp" 13 | 14 | namespace xrob 15 | { 16 | std::string get_tmp_prefix() 17 | { 18 | return xeus::get_tmp_prefix("xrobot"); 19 | } 20 | 21 | std::string get_tmp_suffix() 22 | { 23 | return ".robot"; 24 | } 25 | 26 | std::string get_cell_tmp_file(const std::string& content) 27 | { 28 | return xeus::get_cell_tmp_file(get_tmp_prefix(), 29 | content, 30 | get_tmp_suffix()); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/xinternal_utils.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #ifndef XROB_INTERNAL_UTILS_HPP 12 | #define XROB_INTERNAL_UTILS_HPP 13 | 14 | #include 15 | 16 | namespace xrob 17 | { 18 | std::string get_tmp_prefix(); 19 | std::string get_tmp_suffix(); 20 | std::string get_cell_tmp_file(const std::string& content); 21 | } 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /src/xinterpreter.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2020, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2020, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "nlohmann/json.hpp" 16 | 17 | #include "pybind11_json/pybind11_json.hpp" 18 | 19 | #include "pybind11/functional.h" 20 | #include "pybind11/eval.h" 21 | 22 | #include "xeus/xguid.hpp" 23 | 24 | #include "xeus-python/xinterpreter.hpp" 25 | #include "xeus-python/xtraceback.hpp" 26 | #include "xeus-python/xutils.hpp" 27 | 28 | #include "xeus_robot_config.hpp" 29 | #include "xinternal_utils.hpp" 30 | #include "xtraceback.hpp" 31 | #include "xinterpreter.hpp" 32 | 33 | namespace nl = nlohmann; 34 | 35 | namespace py = pybind11; 36 | using namespace pybind11::literals; 37 | 38 | #define PYTHON_MODULE_REGEX "^%%python module ([a-zA-Z_]+)" 39 | 40 | 41 | void safe_cleanup(const py::object& outputdir, const py::object& progress_updater, const py::object& logger) { 42 | // Clean up the passed outputdir, log in cases of errors 43 | try 44 | { 45 | // Cleanup 46 | outputdir.attr("cleanup")(); 47 | progress_updater.attr("clear")(); 48 | } 49 | catch (py::error_already_set& e) 50 | { 51 | std::string message = "Got error while trying to clean up: " + std::string(py::str(e.trace())); 52 | logger.attr("warning")(message); 53 | } 54 | } 55 | 56 | 57 | 58 | namespace xrob 59 | { 60 | 61 | interpreter::interpreter() 62 | : xpyt::interpreter() 63 | { 64 | } 65 | 66 | interpreter::~interpreter() 67 | { 68 | } 69 | 70 | void interpreter::configure_impl() 71 | { 72 | xpyt::interpreter::configure_impl(); 73 | 74 | py::gil_scoped_acquire acquire; 75 | 76 | py::module os = py::module::import("os"); 77 | py::module logging = py::module::import("logging"); 78 | py::module robot_interpreter = py::module::import("robotframework_interpreter"); 79 | 80 | py::object formatter_cls = py::module::import("traitlets.config.application").attr("LevelFormatter"); 81 | 82 | // Initialize the test suite 83 | m_test_suite = robot_interpreter.attr("init_suite")("name"_a="xeus-robot"); 84 | 85 | // Initialize listeners 86 | m_listeners = py::list(); 87 | m_drivers = py::list(); 88 | m_debug_listener = py::none(); 89 | m_debug_listenerv2 = py::none(); 90 | 91 | m_keywords_listener = robot_interpreter.attr("RobotKeywordsIndexerListener")(); 92 | m_listeners.append(m_keywords_listener); 93 | 94 | m_return_value_listener = robot_interpreter.attr("ReturnValueListener")(); 95 | m_listeners.append(m_return_value_listener); 96 | 97 | m_status_listener = robot_interpreter.attr("StatusEventListener")(); 98 | m_listeners.append(m_status_listener); 99 | 100 | m_listeners.append(robot_interpreter.attr("GlobalVarsListener")()); 101 | 102 | // Library listeners 103 | m_listeners.append(robot_interpreter.attr("SeleniumConnectionsListener")(m_drivers)); 104 | m_listeners.append(robot_interpreter.attr("PlaywrightConnectionsListener")(m_drivers)); 105 | m_listeners.append(robot_interpreter.attr("JupyterConnectionsListener")(m_drivers)); 106 | m_listeners.append(robot_interpreter.attr("AppiumConnectionsListener")(m_drivers)); 107 | m_listeners.append(robot_interpreter.attr("WhiteLibraryListener")(m_drivers)); 108 | 109 | m_debug_adapter = py::none(); 110 | 111 | // Format and redirect all logging to the terminal 112 | py::object formatter = formatter_cls( 113 | "fmt"_a= "%(asctime)s.%(msecs)03d › %(levelname)s › %(name)s › %(process)d › %(message)s", 114 | "datefmt"_a="%Y/%m/%d %H:%M:%S" 115 | ); 116 | 117 | py::object handler = logging.attr("StreamHandler")(m_terminal_stream); 118 | handler.attr("setFormatter")(formatter); 119 | 120 | py::object handlers = py::list(0); 121 | handlers.attr("append")(handler); 122 | 123 | logging.attr("getLogger")().attr("handlers") = handlers; 124 | m_logger.attr("handlers") = handlers; 125 | } 126 | 127 | nl::json interpreter::execute_request_impl( 128 | int execution_count, 129 | const std::string& code, 130 | bool silent, 131 | bool /*store_history*/, 132 | nl::json /*user_expressions*/, 133 | bool /*allow_stdin*/) 134 | { 135 | // Acquire GIL before executing code 136 | py::gil_scoped_acquire acquire; 137 | 138 | std::string filename = get_cell_tmp_file(code); 139 | 140 | // If it's Python code 141 | py::module re = py::module::import("re"); 142 | py::object match = re.attr("match")(PYTHON_MODULE_REGEX, code); 143 | if (!match.is_none()) 144 | { 145 | py::object modulename = py::list(match.attr("groups")())[0]; 146 | 147 | // Extract Python code from the cell 148 | std::string python_code = code; 149 | python_code.erase(0, py::list(match.attr("span")())[1].cast()); 150 | 151 | return execute_python(python_code, modulename, filename, silent); 152 | } 153 | 154 | // Maps source file for debugger/traceback 155 | xpyt::register_filename_mapping(filename, execution_count); 156 | m_test_suite.attr("source") = py::str(filename); 157 | 158 | nl::json kernel_res; 159 | 160 | py::object outputdir = py::module::import("tempfile").attr("TemporaryDirectory")(); 161 | py::module robot_interpreter = py::module::import("robotframework_interpreter"); 162 | py::object partial = py::module::import("functools").attr("partial"); 163 | 164 | // Create progress updater and pass it to the state listener 165 | py::str display_id = py::str(xeus::new_xguid()); 166 | 167 | py::module display = py::module::import("IPython.display"); 168 | 169 | py::object progress_updater = robot_interpreter.attr("ProgressUpdater")( 170 | partial(display.attr("display"), "raw"_a=true, "display_id"_a=display_id), 171 | partial(display.attr("update_display"), "raw"_a=true, "display_id"_a=display_id) 172 | ); 173 | m_status_listener.attr("callback") = progress_updater.attr("update"); 174 | 175 | // Get execution result 176 | py::list result; 177 | try 178 | { 179 | result = robot_interpreter.attr("execute")( 180 | code, m_test_suite, "listeners"_a=m_listeners, "drivers"_a=m_drivers, 181 | "outputdir"_a=outputdir.attr("name"), "logger"_a=m_logger 182 | ); 183 | } 184 | // Execution error (e.g. lib import failed) 185 | catch (py::error_already_set& e) 186 | { 187 | safe_cleanup(outputdir, progress_updater, m_logger); 188 | 189 | xpyt::xerror error = extract_robot_error(e); 190 | 191 | if (!silent) 192 | { 193 | publish_execution_error(error.m_ename, error.m_evalue, error.m_traceback); 194 | } 195 | 196 | kernel_res["status"] = "error"; 197 | kernel_res["ename"] = error.m_ename; 198 | kernel_res["evalue"] = error.m_evalue; 199 | kernel_res["traceback"] = error.m_traceback; 200 | 201 | return kernel_res; 202 | } 203 | 204 | // If the result is None, it means the suite has not been executed, instead 205 | // widgets have been created 206 | if (result[0].is_none()) 207 | { 208 | for (const py::handle& widget: result[1]) 209 | { 210 | display.attr("display")(widget); 211 | } 212 | } 213 | // Otherwise, publish tests report if there is one, stop the execution if tests failed 214 | else 215 | { 216 | if (!result[1].is_none()) 217 | { 218 | publish_execution_result(execution_count, result[1], nl::json::object()); 219 | } 220 | 221 | bool failed = false; 222 | xpyt::xerror error; 223 | error.m_ename = "Task(s) failed"; 224 | error.m_evalue = "Task(s) failed"; 225 | error.m_traceback.push_back(first_error_delimiter(error.m_ename)); 226 | for (const py::handle& test: result[0].attr("suite").attr("tests")) 227 | { 228 | if ((py::hasattr(test, "failed") && xpyt::is_pyobject_true(test.attr("failed"))) || 229 | (py::hasattr(test, "passed") && !xpyt::is_pyobject_true(test.attr("passed")))) 230 | { 231 | failed = true; 232 | 233 | std::stringstream error_msg; 234 | error_msg << "Task " << blue_text(py::str(test.attr("name")).cast()) 235 | << " failed with: " << py::str(test.attr("message")).cast(); 236 | 237 | error.m_traceback.push_back(error_msg.str()); 238 | } 239 | } 240 | 241 | if (failed) 242 | { 243 | if (!silent) 244 | { 245 | error.m_traceback.push_back(last_error_delimiter()); 246 | publish_execution_error(error.m_ename, error.m_evalue, error.m_traceback); 247 | } 248 | 249 | safe_cleanup(outputdir, progress_updater, m_logger); 250 | 251 | kernel_res["status"] = "error"; 252 | kernel_res["ename"] = error.m_ename; 253 | kernel_res["evalue"] = error.m_evalue; 254 | kernel_res["traceback"] = error.m_traceback; 255 | 256 | return kernel_res; 257 | } 258 | } 259 | 260 | // Publish the latest test evaluation 261 | py::object last_test_evaluation = m_return_value_listener.attr("get_last_value")(); 262 | if (!last_test_evaluation.is_none()) 263 | { 264 | display.attr("display")(last_test_evaluation, "raw"_a=true); 265 | } 266 | 267 | safe_cleanup(outputdir, progress_updater, m_logger); 268 | 269 | kernel_res["status"] = "ok"; 270 | kernel_res["user_expressions"] = nl::json::object(); 271 | kernel_res["payload"] = nl::json::array(); 272 | 273 | return kernel_res; 274 | } 275 | 276 | nl::json interpreter::execute_python( 277 | const std::string& code, 278 | py::object modulename, 279 | const std::string& filename, 280 | bool silent) 281 | { 282 | nl::json kernel_res; 283 | 284 | py::module sys = py::module::import("sys"); 285 | py::module types = py::module::import("types"); 286 | py::module linecache = py::module::import("linecache"); 287 | py::module builtins = py::module::import("builtins"); 288 | py::module display = py::module::import("IPython.display"); 289 | 290 | // Create Python module 291 | py::object module = types.attr("ModuleType")(modulename); 292 | 293 | sys.attr("modules")[modulename] = module; 294 | 295 | // Caching the input code 296 | linecache.attr("updatecache")(code, filename); 297 | 298 | // Reset traceback 299 | m_ipython_shell.attr("last_error") = py::none(); 300 | 301 | // Execute it 302 | try 303 | { 304 | py::object compiled_code = builtins.attr("compile")(code, filename, "exec"); 305 | 306 | // Inject display in the scope 307 | module.attr("__dict__")["display"] = display.attr("display"); 308 | 309 | xpyt::exec(compiled_code, module.attr("__dict__")); 310 | 311 | kernel_res["status"] = "ok"; 312 | kernel_res["user_expressions"] = nl::json::object(); 313 | kernel_res["payload"] = nl::json::array(); 314 | 315 | // Keep the Python module name around for library completion 316 | m_python_modules.attr("append")(modulename); 317 | } 318 | catch (py::error_already_set& e) 319 | { 320 | py::object traceback = e.trace() ? e.trace() : py::none(); 321 | m_ipython_shell.attr("showtraceback")( 322 | py::make_tuple(e.type(), e.value(), traceback) 323 | ); 324 | 325 | py::list pyerror = m_ipython_shell.attr("last_error"); 326 | 327 | xpyt::xerror error = xpyt::extract_error(pyerror); 328 | 329 | if (!silent) 330 | { 331 | publish_execution_error(error.m_ename, error.m_evalue, error.m_traceback); 332 | } 333 | 334 | kernel_res["status"] = "error"; 335 | kernel_res["ename"] = error.m_ename; 336 | kernel_res["evalue"] = error.m_evalue; 337 | kernel_res["traceback"] = error.m_traceback; 338 | } 339 | 340 | return kernel_res; 341 | } 342 | 343 | nl::json interpreter::complete_request_impl( 344 | const std::string& code, 345 | int cursor_pos) 346 | { 347 | // Acquire GIL before executing code 348 | py::gil_scoped_acquire acquire; 349 | 350 | // If it's Python code 351 | py::module re = py::module::import("re"); 352 | py::object match = re.attr("match")(PYTHON_MODULE_REGEX, code); 353 | if (!match.is_none()) 354 | { 355 | py::object module_name = py::list(match.attr("groups")())[0]; 356 | 357 | // Extract Python code from the cell 358 | std::string python_code = code; 359 | int header_len = py::list(match.attr("span")())[1].cast(); 360 | python_code.erase(0, header_len); 361 | 362 | nl::json xpython_res = xpyt::interpreter::complete_request_impl(python_code, cursor_pos - header_len); 363 | 364 | // Fix cursor pos in xpython's answer 365 | xpython_res["cursor_start"] = xpython_res["cursor_start"].get() + header_len; 366 | xpython_res["cursor_end"] = xpython_res["cursor_end"].get() + header_len; 367 | 368 | return xpython_res; 369 | } 370 | 371 | py::module robot_interpreter = py::module::import("robotframework_interpreter"); 372 | 373 | nl::json xrobot_res = robot_interpreter.attr("complete")( 374 | code, cursor_pos, m_test_suite, m_keywords_listener, m_python_modules, m_drivers, "logger"_a=m_logger 375 | ); 376 | xrobot_res["status"] = "ok"; 377 | xrobot_res["metadata"] = nl::json::object(); 378 | return xrobot_res; 379 | } 380 | 381 | nl::json interpreter::inspect_request_impl(const std::string& code, 382 | int cursor_pos, 383 | int detail_level) 384 | { 385 | // Acquire GIL before executing code 386 | py::gil_scoped_acquire acquire; 387 | 388 | // If it's Python code 389 | py::module re = py::module::import("re"); 390 | py::object match = re.attr("match")(PYTHON_MODULE_REGEX, code); 391 | if (!match.is_none()) 392 | { 393 | py::object module_name = py::list(match.attr("groups")())[0]; 394 | 395 | // Extract Python code from the cell 396 | std::string python_code = code; 397 | int header_len = py::list(match.attr("span")())[1].cast(); 398 | python_code.erase(0, header_len); 399 | 400 | nl::json xpython_res = xpyt::interpreter::inspect_request_impl(python_code, cursor_pos - header_len, detail_level); 401 | 402 | return xpython_res; 403 | } 404 | 405 | py::module robot_interpreter = py::module::import("robotframework_interpreter"); 406 | 407 | nl::json xrobot_res = robot_interpreter.attr("inspect")( 408 | code, cursor_pos, m_test_suite, m_keywords_listener, detail_level, "logger"_a=m_logger 409 | ); 410 | xrobot_res["status"] = "ok"; 411 | return xrobot_res; 412 | } 413 | 414 | nl::json interpreter::is_complete_request_impl(const std::string& /*code*/) 415 | { 416 | nl::json kernel_res; 417 | return kernel_res; 418 | } 419 | 420 | nl::json interpreter::kernel_info_request_impl() 421 | { 422 | nl::json result; 423 | result["implementation"] = "xeus-robot"; 424 | result["implementation_version"] = XROB_VERSION; 425 | 426 | /* The jupyter-console banner for xeus-robot is the following: 427 | __ _____ _ _ ___ 428 | \ \/ / _ \ | | / __| 429 | > < __/ |_| \__ \ 430 | /_/\_\___|\__,_|___/ 431 | 432 | xeus-robot: a Jupyter lernel for robotframework 433 | */ 434 | 435 | std::string banner = "" 436 | " __ _____ _ _ ___\n" 437 | " \\ \\/ / _ \\ | | / __|\n" 438 | " > < __/ |_| \\__ \\\n" 439 | " /_/\\_\\___|\\__,_|___/\n" 440 | "\n" 441 | " xeus-robot: a Jupyter kernel for robotframework"; 442 | result["banner"] = banner; 443 | // result["debugger"] = true; 444 | 445 | result["language_info"]["name"] = "robotframework"; 446 | result["language_info"]["version"] = "3.2"; 447 | result["language_info"]["mimetype"] = "text/x-robotframework"; 448 | result["language_info"]["file_extension"] = ".robot"; 449 | result["language_info"]["codemirror_mode"] = "robotframework"; 450 | result["language_info"]["pygments_lexer"] = "robotframework"; 451 | 452 | result["help_links"] = nl::json::array(); 453 | result["help_links"][0] = nl::json::object({ 454 | {"text", "RobotFramework Reference"}, 455 | {"url", "https://robotframework.org"} 456 | }); 457 | 458 | result["status"] = "ok"; 459 | return result; 460 | } 461 | 462 | void interpreter::shutdown_request_impl() 463 | { 464 | // Acquire GIL before executing code 465 | py::gil_scoped_acquire acquire; 466 | 467 | py::module robot_interpreter = py::module::import("robotframework_interpreter"); 468 | 469 | // Shutdown drivers 470 | robot_interpreter.attr("shutdown_drivers")(m_drivers); 471 | } 472 | 473 | nl::json interpreter::internal_request_impl(const nl::json& content) 474 | { 475 | py::gil_scoped_acquire acquire; 476 | std::string port = content.value("port", ""); 477 | std::string code = content.value("code", ""); 478 | nl::json reply; 479 | 480 | // Reset traceback 481 | m_ipython_shell.attr("last_error") = py::none(); 482 | 483 | try 484 | { 485 | try 486 | { 487 | m_listeners.attr("remove")(m_debug_listener); 488 | m_listeners.attr("remove")(m_debug_listenerv2); 489 | } 490 | catch(py::error_already_set& e) 491 | { 492 | // Ignoring the exception if the element is not in the list 493 | } 494 | 495 | py::dict scope; 496 | 497 | xpyt::exec(py::str(code), scope); 498 | 499 | m_debug_listener = scope["debug_listener"]; 500 | m_debug_listenerv2 = scope["debug_listenerv2"]; 501 | m_debug_adapter = scope["processor"]; 502 | 503 | m_listeners.append(m_debug_listener); 504 | m_listeners.append(m_debug_listenerv2); 505 | 506 | reply["status"] = "ok"; 507 | } 508 | catch (py::error_already_set& e) 509 | { 510 | // This will grab the latest traceback and set shell.last_error 511 | m_ipython_shell.attr("showtraceback")(); 512 | 513 | py::list pyerror = m_ipython_shell.attr("last_error"); 514 | 515 | xpyt::xerror error = xpyt::extract_error(pyerror); 516 | 517 | publish_execution_error(error.m_ename, error.m_evalue, error.m_traceback); 518 | 519 | reply["status"] = "error"; 520 | reply["ename"] = error.m_ename; 521 | reply["evalue"] = error.m_evalue; 522 | reply["traceback"] = error.m_traceback; 523 | } 524 | return reply; 525 | } 526 | 527 | } 528 | -------------------------------------------------------------------------------- /src/xinterpreter.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2020, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2020, QuantStack 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #ifndef XROB_INTERPRETER_HPP 12 | #define XROB_INTERPRETER_HPP 13 | 14 | #ifdef __GNUC__ 15 | #pragma GCC diagnostic push 16 | #pragma GCC diagnostic ignored "-Wattributes" 17 | #endif 18 | 19 | #include 20 | 21 | #include "nlohmann/json.hpp" 22 | 23 | #include "xeus-python/xinterpreter.hpp" 24 | 25 | namespace nl = nlohmann; 26 | 27 | namespace xrob 28 | { 29 | class interpreter : public xpyt::interpreter 30 | { 31 | public: 32 | 33 | interpreter(); 34 | virtual ~interpreter(); 35 | 36 | protected: 37 | 38 | void configure_impl() override; 39 | 40 | nl::json execute_request_impl(int execution_counter, 41 | const std::string& code, 42 | bool silent, 43 | bool store_history, 44 | nl::json user_expressions, 45 | bool allow_stdin) override; 46 | 47 | nl::json execute_python(const std::string& code, py::object modulename, const std::string& filename, bool silent); 48 | 49 | nl::json complete_request_impl(const std::string& code, int cursor_pos) override; 50 | 51 | nl::json inspect_request_impl(const std::string& code, 52 | int cursor_pos, 53 | int detail_level) override; 54 | 55 | nl::json is_complete_request_impl(const std::string& code) override; 56 | 57 | nl::json kernel_info_request_impl() override; 58 | 59 | void shutdown_request_impl() override; 60 | 61 | nl::json internal_request_impl(const nl::json& content) override; 62 | 63 | py::object m_test_suite; 64 | 65 | py::object m_debug_listener; 66 | py::object m_debug_listenerv2; 67 | py::object m_keywords_listener; 68 | py::object m_return_value_listener; 69 | py::object m_status_listener; 70 | py::list m_listeners; 71 | 72 | py::list m_drivers; 73 | 74 | py::list m_python_modules; 75 | py::object m_debug_adapter; 76 | }; 77 | } 78 | 79 | #ifdef __GNUC__ 80 | #pragma GCC diagnostic pop 81 | #endif 82 | 83 | #endif 84 | -------------------------------------------------------------------------------- /src/xrobodebug_client.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include "zmq_addon.hpp" 12 | #include "nlohmann/json.hpp" 13 | #include "xeus/xmessage.hpp" 14 | #include "xrobodebug_client.hpp" 15 | 16 | #include 17 | #include 18 | 19 | namespace nl = nlohmann; 20 | 21 | namespace xrob 22 | { 23 | xrobodebug_client::xrobodebug_client(zmq::context_t& context, 24 | const xeus::xconfiguration& config, 25 | int socket_linger, 26 | const xdap_tcp_configuration& dap_config, 27 | const event_callback& cb) 28 | : base_type(context, config, socket_linger, dap_config, cb) 29 | { 30 | } 31 | 32 | void xrobodebug_client::handle_event(nl::json message) 33 | { 34 | forward_event(std::move(message)); 35 | } 36 | 37 | nl::json xrobodebug_client::get_stack_frames(int thread_id, int seq) 38 | { 39 | nl::json request = { 40 | {"type", "request"}, 41 | {"seq", seq}, 42 | {"command", "stackTrace"}, 43 | {"arguments", { 44 | {"threadId", thread_id} 45 | }} 46 | }; 47 | 48 | send_dap_request(std::move(request)); 49 | 50 | nl::json reply = wait_for_message([](const nl::json& message) 51 | { 52 | return message["type"] == "response" && message["command"] == "stackTrace"; 53 | }); 54 | return reply["body"]["stackFrames"]; 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/xrobodebug_client.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #ifndef XROB_ROBODEBUG_CLIENT_HPP 12 | #define XROB_ROBODEBUG_CLIENT_HPP 13 | 14 | #include "xeus-zmq/xdap_tcp_client.hpp" 15 | 16 | namespace xrob 17 | { 18 | using xeus::xdap_tcp_client; 19 | using xeus::xdap_tcp_configuration; 20 | 21 | class xrobodebug_client : public xdap_tcp_client 22 | { 23 | public: 24 | 25 | using base_type = xdap_tcp_client; 26 | using event_callback = base_type::event_callback; 27 | 28 | xrobodebug_client(zmq::context_t& context, 29 | const xeus::xconfiguration& config, 30 | int socket_linger, 31 | const xdap_tcp_configuration& dap_config, 32 | const event_callback& cb); 33 | 34 | 35 | virtual ~xrobodebug_client() = default; 36 | 37 | private: 38 | 39 | void handle_event(nl::json message) override; 40 | nl::json get_stack_frames(int thread_id, int seq); 41 | }; 42 | } 43 | 44 | #endif 45 | 46 | -------------------------------------------------------------------------------- /src/xrobot_extension.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef __GNUC__ 18 | #include 19 | #include 20 | #include 21 | #include 22 | #endif 23 | 24 | #include "xeus/xeus_context.hpp" 25 | #include "xeus/xkernel.hpp" 26 | #include "xeus/xkernel_configuration.hpp" 27 | 28 | #include "xeus-zmq/xserver_shell_main.hpp" 29 | 30 | #include "xeus-python/xutils.hpp" 31 | 32 | #include "pybind11/pybind11.h" 33 | 34 | #include "xinterpreter.hpp" 35 | #include "xdebugger.hpp" 36 | 37 | namespace py = pybind11; 38 | 39 | void launch(const py::list args_list) 40 | { 41 | // Extract cli args from Python object 42 | int argc = args_list.size(); 43 | std::vector argv(argc); 44 | 45 | for (int i = 0; i < argc; ++i) 46 | { 47 | argv[i] = (char*)PyUnicode_AsUTF8(args_list[i].ptr()); 48 | } 49 | 50 | if (xpyt::should_print_version(argc, argv.data())) 51 | { 52 | std::clog << "xrobot " << XPYT_VERSION << std::endl; 53 | return; 54 | } 55 | 56 | // Registering SIGSEGV handler 57 | #ifdef __GNUC__ 58 | std::clog << "registering handler for SIGSEGV" << std::endl; 59 | signal(SIGSEGV, xpyt::sigsegv_handler); 60 | 61 | // Registering SIGINT and SIGKILL handlers 62 | signal(SIGKILL, xpyt::sigkill_handler); 63 | #endif 64 | signal(SIGINT, xpyt::sigkill_handler); 65 | 66 | // Instantiating the xeus xinterpreter 67 | using interpreter_ptr = std::unique_ptr; 68 | interpreter_ptr interpreter = interpreter_ptr(new xrob::interpreter()); 69 | 70 | using history_manager_ptr = std::unique_ptr; 71 | history_manager_ptr hist = xeus::make_in_memory_history_manager(); 72 | 73 | #ifdef XEUS_ROBOT_PYPI_WARNING 74 | std::clog << 75 | "WARNING: this instance of xeus-robot has been installed from a PyPI wheel.\n" 76 | "We recommend using a general-purpose package manager instead, such as Conda / Mamba.\n" 77 | << std::endl; 78 | #endif 79 | 80 | std::string connection_filename = xpyt::extract_parameter("-f", argc, argv.data()); 81 | 82 | auto context = xeus::make_context(); 83 | 84 | if (!connection_filename.empty()) 85 | { 86 | xeus::xconfiguration config = xeus::load_configuration(connection_filename); 87 | 88 | xeus::xkernel kernel(config, 89 | xeus::get_user_name(), 90 | std::move(context), 91 | std::move(interpreter), 92 | xeus::make_xserver_shell_main, 93 | std::move(hist), 94 | xeus::make_console_logger(xeus::xlogger::msg_type, 95 | xeus::make_file_logger(xeus::xlogger::content, "xeus.log")), 96 | xrob::make_robot_debugger); 97 | 98 | std::clog << 99 | "Starting xeus-robot kernel...\n\n" 100 | "If you want to connect to this kernel from an other client, you can use" 101 | " the " + connection_filename + " file." 102 | << std::endl; 103 | 104 | kernel.start(); 105 | } 106 | else 107 | { 108 | xeus::xkernel kernel(xeus::get_user_name(), 109 | std::move(context), 110 | std::move(interpreter), 111 | xeus::make_xserver_shell_main, 112 | std::move(hist), 113 | nullptr, 114 | xrob::make_robot_debugger); 115 | 116 | const auto& config = kernel.get_config(); 117 | std::clog << 118 | "Starting xeus-robot kernel...\n\n" 119 | "If you want to connect to this kernel from an other client, just copy" 120 | " and paste the following content inside of a `kernel.json` file. And then run for example:\n\n" 121 | "# jupyter console --existing kernel.json\n\n" 122 | "kernel.json\n```\n{\n" 123 | " \"transport\": \"" + config.m_transport + "\",\n" 124 | " \"ip\": \"" + config.m_ip + "\",\n" 125 | " \"control_port\": " + config.m_control_port + ",\n" 126 | " \"shell_port\": " + config.m_shell_port + ",\n" 127 | " \"stdin_port\": " + config.m_stdin_port + ",\n" 128 | " \"iopub_port\": " + config.m_iopub_port + ",\n" 129 | " \"hb_port\": " + config.m_hb_port + ",\n" 130 | " \"signature_scheme\": \"" + config.m_signature_scheme + "\",\n" 131 | " \"key\": \"" + config.m_key + "\"\n" 132 | "}\n```" 133 | << std::endl; 134 | 135 | kernel.start(); 136 | } 137 | } 138 | 139 | PYBIND11_MODULE(xrobot_extension, m) 140 | { 141 | m.doc() = "Xeus-robot kernel launcher"; 142 | m.def("launch", launch, py::arg("connection_filename") = "", "Launch the Jupyter kernel"); 143 | } 144 | -------------------------------------------------------------------------------- /src/xtraceback.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "xeus-python/xutils.hpp" 17 | #include "xeus-python/xtraceback.hpp" 18 | 19 | #include "xtraceback.hpp" 20 | 21 | #include "pybind11/pybind11.h" 22 | 23 | namespace py = pybind11; 24 | 25 | namespace xrob 26 | { 27 | std::string red_text(const std::string& text) 28 | { 29 | return "\033[0;31m" + text + "\033[0m"; 30 | } 31 | 32 | std::string green_text(const std::string& text) 33 | { 34 | return "\033[0;32m" + text + "\033[0m"; 35 | } 36 | 37 | std::string blue_text(const std::string& text) 38 | { 39 | return "\033[0;34m" + text + "\033[0m"; 40 | } 41 | 42 | std::string first_error_delimiter(const std::string& error_name) 43 | { 44 | std::size_t size(75); 45 | std::stringstream first_frame; 46 | 47 | first_frame << red_text(std::string(size, '-')) << "\n" 48 | << red_text(error_name) << ":\n"; 49 | 50 | return first_frame.str(); 51 | } 52 | 53 | std::string last_error_delimiter() 54 | { 55 | std::size_t size(75); 56 | std::stringstream first_frame; 57 | 58 | first_frame << red_text(std::string(size, '-')) << "\n"; 59 | 60 | return first_frame.str(); 61 | } 62 | 63 | xpyt::xerror extract_robot_error(py::error_already_set& error) 64 | { 65 | xpyt::xerror out; 66 | 67 | // Fetch the error message, it must be released by the C++ exception first 68 | error.restore(); 69 | 70 | py::object py_type; 71 | py::object py_value; 72 | py::object py_tb; 73 | PyErr_Fetch(&py_type.ptr(), &py_value.ptr(), &py_tb.ptr()); 74 | 75 | // This should NOT happen 76 | if (py_type.is_none()) 77 | { 78 | out.m_ename = "Error"; 79 | out.m_evalue = error.what(); 80 | out.m_traceback.push_back(error.what()); 81 | } 82 | else 83 | { 84 | out.m_ename = py::str(py_type.attr("__name__")); 85 | out.m_evalue = py::str(py_value); 86 | 87 | out.m_traceback.push_back(first_error_delimiter(out.m_ename)); 88 | out.m_traceback.push_back(out.m_evalue); 89 | out.m_traceback.push_back(last_error_delimiter()); 90 | } 91 | 92 | return out; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/xtraceback.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #ifndef XROB_TRACEBACK_HPP 12 | #define XROB_TRACEBACK_HPP 13 | 14 | #include 15 | #include 16 | 17 | #include "pybind11/pybind11.h" 18 | 19 | #include "xeus-python/xtraceback.hpp" 20 | 21 | namespace py = pybind11; 22 | 23 | namespace xrob 24 | { 25 | std::string red_text(const std::string& text); 26 | std::string green_text(const std::string& text); 27 | std::string blue_text(const std::string& text); 28 | 29 | std::string first_error_delimiter(const std::string& error_name); 30 | std::string last_error_delimiter(); 31 | 32 | xpyt::xerror extract_robot_error(py::error_already_set& error); 33 | } 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Copyright (c) 2016, Martin Renou, Johan Mabille, Sylvain Corlay, and # 3 | # Wolf Vollprecht # 4 | # Copyright (c) 2016, QuantStack # 5 | # # 6 | # Distributed under the terms of the BSD 3-Clause License. # 7 | # # 8 | # The full license is in the file LICENSE, distributed with this software. # 9 | ############################################################################ 10 | 11 | # Unit tests 12 | # ========== 13 | 14 | cmake_minimum_required(VERSION 3.1) 15 | 16 | if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) 17 | project(xeus-robot-test) 18 | 19 | enable_testing() 20 | 21 | find_package(xeus-robot REQUIRED CONFIG) 22 | endif () 23 | 24 | message(STATUS "Forcing tests build type to Release") 25 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 26 | 27 | include(CheckCXXCompilerFlag) 28 | 29 | string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE) 30 | 31 | if(CMAKE_CXX_COMPILER_ID MATCHES Clang OR CMAKE_CXX_COMPILER_ID MATCHES GNU OR CMAKE_CXX_COMPILER_ID MATCHES Intel) 32 | add_compile_options(-Wunused-parameter -Wextra -Wreorder -Wconversion -Wsign-conversion) 33 | 34 | CHECK_CXX_COMPILER_FLAG(-march=native HAS_MARCH_NATIVE) 35 | if (HAS_MARCH_NATIVE) 36 | add_compile_options(-march=native) 37 | endif() 38 | endif() 39 | 40 | if(CMAKE_CXX_COMPILER_ID MATCHES MSVC) 41 | add_compile_options(/EHsc /MP /bigobj) 42 | set(CMAKE_EXE_LINKER_FLAGS /MANIFEST:NO) 43 | endif() 44 | 45 | if(XROB_DOWNLOAD_GTEST OR GTEST_SRC_DIR) 46 | if(XROB_DOWNLOAD_GTEST) 47 | # Download and unpack googletest at configure time 48 | configure_file(downloadGTest.cmake.in googletest-download/CMakeLists.txt) 49 | else() 50 | # Copy local source of googletest at configure time 51 | configure_file(copyGTest.cmake.in googletest-download/CMakeLists.txt) 52 | endif() 53 | execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . 54 | RESULT_VARIABLE result 55 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) 56 | if(result) 57 | message(FATAL_ERROR "CMake step for googletest failed: ${result}") 58 | endif() 59 | execute_process(COMMAND ${CMAKE_COMMAND} --build . 60 | RESULT_VARIABLE result 61 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download ) 62 | if(result) 63 | message(FATAL_ERROR "Build step for googletest failed: ${result}") 64 | endif() 65 | 66 | # Add googletest directly to our build. This defines 67 | # the gtest and gtest_main targets. 68 | add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src 69 | ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) 70 | 71 | set(GTEST_INCLUDE_DIRS "${gtest_SOURCE_DIR}/include") 72 | set(GTEST_BOTH_LIBRARIES gtest_main gtest) 73 | else() 74 | find_package(GTest REQUIRED) 75 | endif() 76 | 77 | find_package(Threads) 78 | 79 | include_directories(${GTEST_INCLUDE_DIRS} SYSTEM) 80 | 81 | set(XEUS_ROBOT_TESTS 82 | test_xrobot.cpp 83 | xeus_client.hpp 84 | xeus_client.cpp 85 | ) 86 | 87 | add_executable(test_xeus_robot ${XEUS_ROBOT_TESTS}) 88 | if(XROB_DOWNLOAD_GTEST OR GTEST_SRC_DIR) 89 | add_dependencies(test_xeus_robot gtest_main) 90 | endif() 91 | 92 | if (APPLE) 93 | set_target_properties(test_xeus_robot PROPERTIES 94 | MACOSX_RPATH ON 95 | ) 96 | else() 97 | set_target_properties(test_xeus_robot PROPERTIES 98 | BUILD_WITH_INSTALL_RPATH 1 99 | SKIP_BUILD_RPATH FALSE 100 | ) 101 | endif() 102 | 103 | set_target_properties(test_xeus_robot PROPERTIES 104 | INSTALL_RPATH_USE_LINK_PATH TRUE 105 | ) 106 | 107 | include_directories(${PYTHON_INCLUDE_DIRS}) 108 | 109 | target_link_libraries(test_xeus_robot ${PYTHON_LIBRARIES} xeus-zmq ${GTEST_BOTH_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) 110 | target_include_directories(test_xeus_robot PRIVATE ${XEUS_ROBOT_INCLUDE_DIR}) 111 | 112 | add_custom_target(xtest COMMAND test_xeus_robot DEPENDS test_xeus_robot) 113 | 114 | -------------------------------------------------------------------------------- /test/copyGTest.cmake.in: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Copyright (c) 2016, Johan Mabille, Sylvain Corlay, and Wolf Vollprecht # 3 | # # 4 | # Distributed under the terms of the BSD 3-Clause License. # 5 | # # 6 | # The full license is in the file LICENSE, distributed with this software. # 7 | ############################################################################ 8 | 9 | cmake_minimum_required(VERSION 2.8.2) 10 | 11 | project(googletest-download NONE) 12 | 13 | include(ExternalProject) 14 | ExternalProject_Add(googletest 15 | URL "${GTEST_SRC_DIR}" 16 | SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" 17 | BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" 18 | CONFIGURE_COMMAND "" 19 | BUILD_COMMAND "" 20 | INSTALL_COMMAND "" 21 | TEST_COMMAND "" 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /test/downloadGTest.cmake.in: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Copyright (c) 2016, Johan Mabille, Sylvain Corlay, and Wolf Vollprecht # 3 | # # 4 | # Distributed under the terms of the BSD 3-Clause License. # 5 | # # 6 | # The full license is in the file LICENSE, distributed with this software. # 7 | ############################################################################ 8 | 9 | cmake_minimum_required(VERSION 2.8.2) 10 | 11 | project(googletest-download NONE) 12 | 13 | include(ExternalProject) 14 | ExternalProject_Add(googletest 15 | GIT_REPOSITORY https://github.com/google/googletest.git 16 | GIT_TAG release-1.8.0 17 | SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" 18 | BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" 19 | CONFIGURE_COMMAND "" 20 | BUILD_COMMAND "" 21 | INSTALL_COMMAND "" 22 | TEST_COMMAND "" 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /test/test_xrobot.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2020, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "xeus/xsystem.hpp" 20 | 21 | #include "gtest/gtest.h" 22 | #include "xeus_client.hpp" 23 | 24 | using namespace std::chrono_literals; 25 | 26 | nl::json make_shutdown_request() 27 | { 28 | nl::json req = { 29 | {"restart", false} 30 | }; 31 | return req; 32 | } 33 | 34 | nl::json make_kernel_info_request() 35 | { 36 | nl::json req = nl::json::object(); 37 | return req; 38 | } 39 | 40 | nl::json make_execute_request(const std::string& code) 41 | { 42 | nl::json req = { 43 | {"code", code}, 44 | }; 45 | return req; 46 | } 47 | 48 | namespace 49 | { 50 | const std::string KERNEL_JSON = "kernel-debug.json"; 51 | } 52 | 53 | void dump_connection_file() 54 | { 55 | static bool dumped = false; 56 | static std::string connection_file = R"( 57 | { 58 | "shell_port": 60779, 59 | "iopub_port": 55691, 60 | "stdin_port": 56973, 61 | "control_port": 56505, 62 | "hb_port": 45551, 63 | "ip": "127.0.0.1", 64 | "key": "6ef0855c-5cba319b6d05552c44a8ac90", 65 | "transport": "tcp", 66 | "signature_scheme": "hmac-sha256", 67 | "kernel_name": "xcpp" 68 | } 69 | )"; 70 | if(!dumped) 71 | { 72 | std::ofstream out(KERNEL_JSON); 73 | out << connection_file; 74 | dumped = true; 75 | } 76 | } 77 | 78 | void start_kernel() 79 | { 80 | dump_connection_file(); 81 | std::thread kernel([]() 82 | { 83 | std::string cmd = "xrobot -f " + KERNEL_JSON + "&"; 84 | int ret2 = std::system(cmd.c_str()); 85 | }); 86 | std::this_thread::sleep_for(2s); 87 | kernel.detach(); 88 | } 89 | 90 | std::condition_variable cv; 91 | std::mutex mcv; 92 | bool done = false; 93 | 94 | void notify_done() 95 | { 96 | { 97 | std::lock_guard lk(mcv); 98 | done = true; 99 | } 100 | cv.notify_one(); 101 | } 102 | 103 | void run_timer() 104 | { 105 | std::unique_lock lk(mcv); 106 | if (!cv.wait_for(lk, std::chrono::seconds(20), []() { return done; })) 107 | { 108 | std::cout << "Unit test time out !!" << std::endl; 109 | #ifndef WIN32 110 | std::string cmd = "killall xrobot"; 111 | int ret = std::system(cmd.c_str()); 112 | std::cout << "killall xrobot returned " << ret << std::endl; 113 | #endif 114 | std::terminate(); 115 | } 116 | } 117 | 118 | void start_timer() 119 | { 120 | done = false; 121 | std::thread t(run_timer); 122 | t.detach(); 123 | } 124 | 125 | TEST(xrobot, kernel_info) 126 | { 127 | start_kernel(); 128 | start_timer(); 129 | zmq::context_t context; 130 | { 131 | std::cout << "Instantiating client" << std::endl; 132 | xeus_logger_client client(context, "xrobot_client", xeus::load_configuration(KERNEL_JSON), "kernel_info.log"); 133 | 134 | std::cout << "Sending kernel_info_request" << std::endl; 135 | client.send_on_control("kernel_info_request", make_kernel_info_request()); 136 | nl::json res = client.receive_on_control(); 137 | std::cout << "received: " << std::endl << res.dump(4) << std::endl; 138 | 139 | client.send_on_control("shutdown_request", make_shutdown_request()); 140 | client.receive_on_control(); 141 | 142 | std::this_thread::sleep_for(2s); 143 | notify_done(); 144 | } 145 | } 146 | 147 | TEST(xrobot, execute) 148 | { 149 | start_kernel(); 150 | start_timer(); 151 | zmq::context_t context; 152 | { 153 | std::cout << "Instantiating client" << std::endl; 154 | xeus_logger_client client(context, "xrobot_client", xeus::load_configuration(KERNEL_JSON), "kernel_info.log"); 155 | 156 | std::string code1 = R"( 157 | *** Settings *** 158 | 159 | Library String 160 | 161 | *** Variables *** 162 | 163 | ${VARNAME} Coucou 164 | )"; 165 | 166 | std::cout << "Sending execute_request" < 12 | #include 13 | #include 14 | #include 15 | 16 | #include "zmq_addon.hpp" 17 | 18 | #include "xeus_client.hpp" 19 | #include "xeus/xguid.hpp" 20 | #include "xeus/xmessage.hpp" 21 | 22 | #include "xeus-zmq/xmiddleware.hpp" 23 | #include "xeus-zmq/xzmq_serializer.hpp" 24 | 25 | using namespace std::chrono_literals; 26 | 27 | /*********************************** 28 | * xeus-client_base implementation * 29 | ***********************************/ 30 | 31 | xeus_client_base::xeus_client_base(zmq::context_t& context, 32 | const std::string& user_name, 33 | const xeus::xconfiguration& config) 34 | : p_shell_authentication(xeus::make_xauthentication(config.m_signature_scheme, config.m_key)) 35 | , p_control_authentication(xeus::make_xauthentication(config.m_signature_scheme, config.m_key)) 36 | , p_iopub_authentication(xeus::make_xauthentication(config.m_signature_scheme, config.m_key)) 37 | , m_shell(context, zmq::socket_type::dealer) 38 | , m_control(context, zmq::socket_type::dealer) 39 | , m_iopub(context, zmq::socket_type::sub) 40 | , m_shell_end_point("") 41 | , m_control_end_point("") 42 | , m_iopub_end_point("") 43 | , m_user_name(user_name) 44 | , m_session_id(xeus::new_xguid()) 45 | { 46 | m_shell_end_point = xeus::get_end_point(config.m_transport, config.m_ip, config.m_shell_port); 47 | m_control_end_point = xeus::get_end_point(config.m_transport, config.m_ip, config.m_control_port); 48 | m_iopub_end_point = xeus::get_end_point(config.m_transport, config.m_ip, config.m_iopub_port); 49 | 50 | m_shell.connect(m_shell_end_point); 51 | m_control.connect(m_control_end_point); 52 | m_iopub.connect(m_iopub_end_point); 53 | } 54 | 55 | xeus_client_base::~xeus_client_base() 56 | { 57 | m_shell.disconnect(m_shell_end_point); 58 | m_control.disconnect(m_control_end_point); 59 | m_iopub.disconnect(m_iopub_end_point); 60 | } 61 | 62 | void xeus_client_base::send_on_shell(nl::json header, 63 | nl::json parent_header, 64 | nl::json metadata, 65 | nl::json content) 66 | { 67 | send_message(std::move(header), 68 | std::move(parent_header), 69 | std::move(metadata), 70 | std::move(content), 71 | m_shell, 72 | *p_shell_authentication); 73 | } 74 | 75 | nl::json xeus_client_base::receive_on_shell() 76 | { 77 | return receive_message(m_shell, *p_shell_authentication); 78 | } 79 | 80 | void xeus_client_base::send_on_control(nl::json header, 81 | nl::json parent_header, 82 | nl::json metadata, 83 | nl::json content) 84 | { 85 | send_message(std::move(header), 86 | std::move(parent_header), 87 | std::move(metadata), 88 | std::move(content), 89 | m_control, 90 | *p_control_authentication); 91 | } 92 | 93 | nl::json xeus_client_base::receive_on_control() 94 | { 95 | return receive_message(m_control, *p_control_authentication); 96 | } 97 | 98 | void xeus_client_base::subscribe_iopub(const std::string& filter) 99 | { 100 | m_iopub.setsockopt(ZMQ_SUBSCRIBE, filter.c_str(), filter.length()); 101 | } 102 | 103 | void xeus_client_base::unsubscribe_iopub(const std::string& filter) 104 | { 105 | m_iopub.setsockopt(ZMQ_UNSUBSCRIBE, filter.c_str(), filter.length()); 106 | } 107 | 108 | nl::json xeus_client_base::receive_on_iopub() 109 | { 110 | zmq::multipart_t wire_msg; 111 | wire_msg.recv(m_iopub); 112 | xeus::xpub_message msg = xeus::xzmq_serializer::deserialize_iopub(wire_msg, *p_iopub_authentication); 113 | 114 | nl::json res = aggregate(msg.header(), 115 | msg.parent_header(), 116 | msg.metadata(), 117 | msg.content()); 118 | res["topic"] = msg.topic(); 119 | return res; 120 | } 121 | 122 | nl::json xeus_client_base::make_header(const std::string& msg_type) const 123 | { 124 | return xeus::make_header(msg_type, m_user_name, m_session_id); 125 | } 126 | 127 | void xeus_client_base::send_message(nl::json header, 128 | nl::json parent_header, 129 | nl::json metadata, 130 | nl::json content, 131 | zmq::socket_t& socket, 132 | const xeus::xauthentication& auth) 133 | { 134 | xeus::xmessage msg(xeus::xmessage::guid_list(), 135 | std::move(header), 136 | std::move(parent_header), 137 | std::move(metadata), 138 | std::move(content), 139 | xeus::buffer_sequence()); 140 | 141 | zmq::multipart_t wire_msg = xeus::xzmq_serializer::serialize(std::move(msg), auth); 142 | wire_msg.send(socket); 143 | } 144 | 145 | nl::json xeus_client_base::receive_message(zmq::socket_t& socket, 146 | const xeus::xauthentication& auth) 147 | { 148 | zmq::multipart_t wire_msg; 149 | wire_msg.recv(socket); 150 | xeus::xmessage msg = xeus::xzmq_serializer::deserialize(wire_msg, auth); 151 | 152 | return aggregate(msg.header(), 153 | msg.parent_header(), 154 | msg.metadata(), 155 | msg.content()); 156 | } 157 | 158 | nl::json xeus_client_base::aggregate(const nl::json& header, 159 | const nl::json& parent_header, 160 | const nl::json& metadata, 161 | const nl::json& content) const 162 | { 163 | nl::json result; 164 | result["header"] = header; 165 | result["parent_header"] = parent_header; 166 | result["metadata"] = metadata; 167 | result["content"] = content; 168 | return result; 169 | } 170 | 171 | /************************************* 172 | * xeus_logger_client implementation * 173 | *************************************/ 174 | 175 | xeus_logger_client::xeus_logger_client(zmq::context_t& context, 176 | const std::string& user_name, 177 | const xeus::xconfiguration& config, 178 | const std::string& file_name) 179 | : xeus_client_base(context, user_name, config) 180 | , m_file_name(file_name) 181 | , m_iopub_stopped(false) 182 | { 183 | std::ofstream out(m_file_name); 184 | out << "STARTING CLIENT" << std::endl; 185 | base_type::subscribe_iopub(""); 186 | std::thread iopub_thread(&xeus_logger_client::poll_iopub, this); 187 | iopub_thread.detach(); 188 | } 189 | 190 | xeus_logger_client::~xeus_logger_client() 191 | { 192 | while(!m_iopub_stopped) 193 | { 194 | std::this_thread::sleep_for(100ms); 195 | } 196 | } 197 | 198 | void xeus_logger_client::send_on_shell(const std::string& msg_type, nl::json content) 199 | { 200 | nl::json header = base_type::make_header(msg_type); 201 | log_message(base_type::aggregate(header, 202 | nl::json::object(), 203 | nl::json::object(), 204 | content)); 205 | base_type::send_on_shell(std::move(header), 206 | nl::json::object(), 207 | nl::json::object(), 208 | std::move(content)); 209 | } 210 | 211 | void xeus_logger_client::send_on_control(const std::string& msg_type, nl::json content) 212 | { 213 | nl::json header = base_type::make_header(msg_type); 214 | log_message(base_type::aggregate(header, 215 | nl::json::object(), 216 | nl::json::object(), 217 | content)); 218 | base_type::send_on_control(std::move(header), 219 | nl::json::object(), 220 | nl::json::object(), 221 | std::move(content)); 222 | } 223 | 224 | nl::json xeus_logger_client::receive_on_shell() 225 | { 226 | nl::json msg = base_type::receive_on_shell(); 227 | log_message(msg); 228 | return msg; 229 | } 230 | 231 | nl::json xeus_logger_client::receive_on_control() 232 | { 233 | nl::json msg = base_type::receive_on_control(); 234 | log_message(msg); 235 | return msg; 236 | } 237 | 238 | std::size_t xeus_logger_client::iopub_queue_size() const 239 | { 240 | std::lock_guard guard(m_queue_mutex); 241 | return m_message_queue.size(); 242 | } 243 | 244 | nl::json xeus_logger_client::pop_iopub_message() 245 | { 246 | std::lock_guard guard(m_queue_mutex); 247 | nl::json res = m_message_queue.back(); 248 | m_message_queue.pop(); 249 | return res; 250 | } 251 | 252 | nl::json xeus_logger_client::wait_for_debug_event(const std::string& event) 253 | { 254 | bool event_found = false; 255 | nl::json msg; 256 | while(!event_found) 257 | { 258 | std::size_t s = iopub_queue_size(); 259 | if(s != 0) 260 | { 261 | msg = pop_iopub_message(); 262 | if(msg["topic"] == "debug_event" && msg["content"]["event"] == event) 263 | { 264 | event_found = true; 265 | } 266 | } 267 | else 268 | { 269 | std::unique_lock lk(m_notify_mutex); 270 | if(iopub_queue_size()) 271 | { 272 | continue; 273 | } 274 | m_notify_cond.wait(lk); 275 | } 276 | } 277 | return msg; 278 | } 279 | 280 | void xeus_logger_client::poll_iopub() 281 | { 282 | while(true) 283 | { 284 | nl::json msg = base_type::receive_on_iopub(); 285 | { 286 | std::unique_lock lk(m_notify_mutex); 287 | std::unique_lock guard(m_queue_mutex); 288 | m_message_queue.push(msg); 289 | guard.unlock(); 290 | lk.unlock(); 291 | m_notify_cond.notify_one(); 292 | } 293 | std::string topic = msg["topic"]; 294 | std::size_t topic_size = topic.size(); 295 | log_message(std::move(msg)); 296 | if(topic.substr(topic_size - 8, topic_size) == "shutdown") 297 | { 298 | std::cout << "Received shutdown, exiting" << std::endl; 299 | break; 300 | } 301 | } 302 | m_iopub_stopped = true; 303 | } 304 | 305 | void xeus_logger_client::log_message(nl::json msg) 306 | { 307 | std::lock_guard guard(m_file_mutex); 308 | std::ofstream out(m_file_name, std::ios_base::app); 309 | out << msg.dump(4) << std::endl; 310 | } 311 | 312 | -------------------------------------------------------------------------------- /test/xeus_client.hpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * Copyright (c) 2018, Martin Renou, Johan Mabille, Sylvain Corlay, and * 3 | * Wolf Vollprecht * 4 | * Copyright (c) 2018, QuantStack * 5 | * * 6 | * Distributed under the terms of the BSD 3-Clause License. * 7 | * * 8 | * The full license is in the file LICENSE, distributed with this software. * 9 | ****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "zmq.hpp" 17 | 18 | #include "nlohmann/json.hpp" 19 | 20 | #include "xeus/xkernel_configuration.hpp" 21 | 22 | #include "xeus-zmq/xauthentication.hpp" 23 | 24 | // Base class for clients, provides an API to 25 | // send and receive messages, but nothing more ;) 26 | 27 | namespace nl = nlohmann; 28 | 29 | class xeus_client_base 30 | { 31 | public: 32 | 33 | xeus_client_base(zmq::context_t& context, 34 | const std::string& user_name, 35 | const xeus::xconfiguration& config); 36 | 37 | virtual ~xeus_client_base(); 38 | 39 | void subscribe_iopub(const std::string& filter); 40 | void unsubscribe_iopub(const std::string& filter); 41 | 42 | protected: 43 | 44 | void send_on_shell(nl::json header, 45 | nl::json parent_header, 46 | nl::json metadata, 47 | nl::json content); 48 | nl::json receive_on_shell(); 49 | 50 | void send_on_control(nl::json header, 51 | nl::json parent_header, 52 | nl::json metadata, 53 | nl::json content); 54 | nl::json receive_on_control(); 55 | 56 | nl::json receive_on_iopub(); 57 | 58 | nl::json make_header(const std::string& msg_type) const; 59 | nl::json aggregate(const nl::json& header, 60 | const nl::json& parent_header, 61 | const nl::json& metadata, 62 | const nl::json& content) const; 63 | 64 | private: 65 | 66 | void send_message(nl::json header, 67 | nl::json parent_header, 68 | nl::json metadata, 69 | nl::json content, 70 | zmq::socket_t& socket, 71 | const xeus::xauthentication& auth); 72 | 73 | nl::json receive_message(zmq::socket_t& socket, 74 | const xeus::xauthentication& auth); 75 | 76 | using authentication_ptr = std::unique_ptr; 77 | authentication_ptr p_shell_authentication; 78 | authentication_ptr p_control_authentication; 79 | authentication_ptr p_iopub_authentication; 80 | 81 | zmq::socket_t m_shell; 82 | zmq::socket_t m_control; 83 | zmq::socket_t m_iopub; 84 | 85 | std::string m_shell_end_point; 86 | std::string m_control_end_point; 87 | std::string m_iopub_end_point; 88 | 89 | std::string m_user_name; 90 | std::string m_session_id; 91 | }; 92 | 93 | // Client that logs sent and received messages. 94 | // Runs the iopub poller in a dedicated thread and 95 | // push messages in a queue for future usage. 96 | class xeus_logger_client : public xeus_client_base 97 | { 98 | public: 99 | 100 | xeus_logger_client(zmq::context_t& context, 101 | const std::string& user_name, 102 | const xeus::xconfiguration& config, 103 | const std::string& file_name); 104 | 105 | virtual ~xeus_logger_client(); 106 | 107 | void send_on_shell(const std::string& msg_type, nl::json content); 108 | void send_on_control(const std::string& msg_type, nl::json content); 109 | 110 | nl::json receive_on_shell(); 111 | nl::json receive_on_control(); 112 | 113 | std::size_t iopub_queue_size() const; 114 | nl::json pop_iopub_message(); 115 | 116 | nl::json wait_for_debug_event(const std::string& event); 117 | 118 | private: 119 | 120 | using base_type = xeus_client_base; 121 | 122 | void poll_iopub(); 123 | void log_message(nl::json msg); 124 | 125 | std::string m_file_name; 126 | bool m_iopub_stopped; 127 | std::queue m_message_queue; 128 | std::mutex m_file_mutex; 129 | mutable std::mutex m_queue_mutex; 130 | std::mutex m_notify_mutex; 131 | std::condition_variable m_notify_cond; 132 | }; 133 | 134 | --------------------------------------------------------------------------------