robotframework-rpycremote 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- robotframework_rpycremote-0.0.1/.github/workflows/pipeline.yml +90 -0
- robotframework_rpycremote-0.0.1/.github/workflows/publish-to-pypi.yml +39 -0
- robotframework_rpycremote-0.0.1/.gitignore +12 -0
- robotframework_rpycremote-0.0.1/LICENSE.txt +21 -0
- robotframework_rpycremote-0.0.1/PKG-INFO +57 -0
- robotframework_rpycremote-0.0.1/README.rst +36 -0
- robotframework_rpycremote-0.0.1/RPyCRobotRemote/RPyCRobotRemoteClient.py +165 -0
- robotframework_rpycremote-0.0.1/RPyCRobotRemote/RPyCRobotRemoteServer.py +204 -0
- robotframework_rpycremote-0.0.1/RPyCRobotRemote/__init__.py +58 -0
- robotframework_rpycremote-0.0.1/RPyCRobotRemote/_version.py +16 -0
- robotframework_rpycremote-0.0.1/pyproject.toml +38 -0
- robotframework_rpycremote-0.0.1/robotframework_rpycremote.egg-info/PKG-INFO +57 -0
- robotframework_rpycremote-0.0.1/robotframework_rpycremote.egg-info/SOURCES.txt +20 -0
- robotframework_rpycremote-0.0.1/robotframework_rpycremote.egg-info/dependency_links.txt +1 -0
- robotframework_rpycremote-0.0.1/robotframework_rpycremote.egg-info/requires.txt +2 -0
- robotframework_rpycremote-0.0.1/robotframework_rpycremote.egg-info/top_level.txt +1 -0
- robotframework_rpycremote-0.0.1/setup.cfg +4 -0
- robotframework_rpycremote-0.0.1/setup.py +9 -0
- robotframework_rpycremote-0.0.1/test/client.py +19 -0
- robotframework_rpycremote-0.0.1/test/provider.py +89 -0
- robotframework_rpycremote-0.0.1/test/server.py +48 -0
- robotframework_rpycremote-0.0.1/test/test.robot +35 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Pipeline Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ 'main' ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ 'main' ]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: 0 0 * * *
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
flake8:
|
|
16
|
+
strategy:
|
|
17
|
+
matrix:
|
|
18
|
+
os: [ 'ubuntu-latest' ]
|
|
19
|
+
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
|
|
20
|
+
runs-on: ${{ matrix.os }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
pip install -U flake8 flake8-polyfill robotframework
|
|
31
|
+
- name: Analysing the code with flake8
|
|
32
|
+
run: |
|
|
33
|
+
flake8 $(git ls-files '*.py') --count --show-source --max-complexity=11 --statistics
|
|
34
|
+
|
|
35
|
+
pylint:
|
|
36
|
+
strategy:
|
|
37
|
+
matrix:
|
|
38
|
+
os: [ 'ubuntu-latest' ]
|
|
39
|
+
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
|
|
40
|
+
runs-on: ${{ matrix.os }}
|
|
41
|
+
steps:
|
|
42
|
+
- uses: actions/checkout@v4
|
|
43
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
44
|
+
uses: actions/setup-python@v5
|
|
45
|
+
with:
|
|
46
|
+
python-version: ${{ matrix.python-version }}
|
|
47
|
+
- name: Install dependencies
|
|
48
|
+
run: |
|
|
49
|
+
python -m pip install --upgrade pip
|
|
50
|
+
pip install -U setuptools_scm setuptools pylint robotframework pyyaml rpyc
|
|
51
|
+
- name: Analysing the code with pylint
|
|
52
|
+
run: |
|
|
53
|
+
pylint --module-naming-style=any $(git ls-files '*.py')
|
|
54
|
+
|
|
55
|
+
robot-framework:
|
|
56
|
+
strategy:
|
|
57
|
+
matrix:
|
|
58
|
+
os: [ 'ubuntu-latest', 'windows-latest' ]
|
|
59
|
+
python-version: [ '3.8', '3.9', '3.10', '3.11' ]
|
|
60
|
+
robotframework-version: [ '5.0.1', '6.0.2', '6.1', '7.0', 'latest' ]
|
|
61
|
+
runs-on: ${{ matrix.os }}
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/checkout@v4
|
|
64
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
65
|
+
uses: actions/setup-python@v5
|
|
66
|
+
with:
|
|
67
|
+
python-version: ${{ matrix.python-version }}
|
|
68
|
+
- name: Upgrade pip
|
|
69
|
+
run: |
|
|
70
|
+
python -m pip install --upgrade pip
|
|
71
|
+
pip install -U setuptools_scm setuptools
|
|
72
|
+
- name: Install PyYaml
|
|
73
|
+
run: |
|
|
74
|
+
pip install pyyaml
|
|
75
|
+
- name: Install robotframework latest
|
|
76
|
+
if: ${{ matrix.robotframework-version == 'latest' }}
|
|
77
|
+
run: |
|
|
78
|
+
pip install -U robotframework
|
|
79
|
+
- name: Install robotframework ${{ matrix.robotframework-version }}
|
|
80
|
+
if: ${{ matrix.robotframework-version != 'latest' }}
|
|
81
|
+
run: |
|
|
82
|
+
pip install robotframework==${{ matrix.robotframework-version }}
|
|
83
|
+
- name: Install RPyCRobotRemote
|
|
84
|
+
run: |
|
|
85
|
+
pip install -e .
|
|
86
|
+
- name: Executing Robot Framework Tests
|
|
87
|
+
run: |
|
|
88
|
+
python test/server.py 2>&1 &
|
|
89
|
+
sleep 3
|
|
90
|
+
robot test/ 2>&1
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# This workflow will upload a Python Package using Twine when a release is created
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
|
3
|
+
|
|
4
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
5
|
+
# They are provided by a third-party and are governed by
|
|
6
|
+
# separate terms of service, privacy policy, and support
|
|
7
|
+
# documentation.
|
|
8
|
+
|
|
9
|
+
name: Upload Python Package
|
|
10
|
+
|
|
11
|
+
on:
|
|
12
|
+
release:
|
|
13
|
+
types: [published]
|
|
14
|
+
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
deploy:
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@v4
|
|
25
|
+
- name: Set up Python
|
|
26
|
+
uses: actions/setup-python@v5
|
|
27
|
+
with:
|
|
28
|
+
python-version: '3.x'
|
|
29
|
+
- name: Install dependencies
|
|
30
|
+
run: |
|
|
31
|
+
python -m pip install --upgrade pip
|
|
32
|
+
pip install -U build
|
|
33
|
+
- name: Build package
|
|
34
|
+
run: python -m build
|
|
35
|
+
- name: Publish package
|
|
36
|
+
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
|
37
|
+
with:
|
|
38
|
+
user: ${{ secrets.PYPI_API_USERNAME }}
|
|
39
|
+
password: ${{ secrets.PYPI_API_PASSWORD }}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 René Lehfeld
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: robotframework-rpycremote
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Robot Framework Remote Library based on RPyC
|
|
5
|
+
Author-email: René Lehfeld <54720674+rlehfeld@users.noreply.github.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rlehfeld/robotframework-rpycremote
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/rlehfeld/robotframework-rpycremote/issues
|
|
9
|
+
Project-URL: Source, https://github.com/rlehfeld/robotframework-rpycremote/
|
|
10
|
+
Keywords: robotframework,robot-framework,rpyc
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Environment :: Other Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
|
+
Description-Content-Type: text/x-rst
|
|
18
|
+
License-File: LICENSE.txt
|
|
19
|
+
Requires-Dist: robotframework>=5.0.1
|
|
20
|
+
Requires-Dist: rpyc>=6.0.0
|
|
21
|
+
|
|
22
|
+
RPyC Robot Remote Library
|
|
23
|
+
============================
|
|
24
|
+
Generic Robot Framework Remote library based on RPyC
|
|
25
|
+
|
|
26
|
+
Installation
|
|
27
|
+
============
|
|
28
|
+
Install the latest release via PyPi using pip:
|
|
29
|
+
|
|
30
|
+
::
|
|
31
|
+
|
|
32
|
+
pip install robotframework-rpycremote
|
|
33
|
+
|
|
34
|
+
Or add to your ``conda.yaml`` file:
|
|
35
|
+
|
|
36
|
+
::
|
|
37
|
+
|
|
38
|
+
- pip:
|
|
39
|
+
- robotframework-rpycremote
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
In oder to help with development you can directly install from GitHub via:
|
|
43
|
+
|
|
44
|
+
::
|
|
45
|
+
|
|
46
|
+
pip install git+https://github.com/rlehfeld/robotframework-rpycremote.git
|
|
47
|
+
|
|
48
|
+
Or add to your ``conda.yaml`` file:
|
|
49
|
+
|
|
50
|
+
::
|
|
51
|
+
|
|
52
|
+
- pip:
|
|
53
|
+
- git+https://github.com/rlehfeld/robotframework-rpycremote.git
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Usage
|
|
57
|
+
=====
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
RPyC Robot Remote Library
|
|
2
|
+
============================
|
|
3
|
+
Generic Robot Framework Remote library based on RPyC
|
|
4
|
+
|
|
5
|
+
Installation
|
|
6
|
+
============
|
|
7
|
+
Install the latest release via PyPi using pip:
|
|
8
|
+
|
|
9
|
+
::
|
|
10
|
+
|
|
11
|
+
pip install robotframework-rpycremote
|
|
12
|
+
|
|
13
|
+
Or add to your ``conda.yaml`` file:
|
|
14
|
+
|
|
15
|
+
::
|
|
16
|
+
|
|
17
|
+
- pip:
|
|
18
|
+
- robotframework-rpycremote
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
In oder to help with development you can directly install from GitHub via:
|
|
22
|
+
|
|
23
|
+
::
|
|
24
|
+
|
|
25
|
+
pip install git+https://github.com/rlehfeld/robotframework-rpycremote.git
|
|
26
|
+
|
|
27
|
+
Or add to your ``conda.yaml`` file:
|
|
28
|
+
|
|
29
|
+
::
|
|
30
|
+
|
|
31
|
+
- pip:
|
|
32
|
+
- git+https://github.com/rlehfeld/robotframework-rpycremote.git
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
Usage
|
|
36
|
+
=====
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Client Implementation for RPyCRobotRemote
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import functools
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Callable
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
import rpyc
|
|
10
|
+
from robot.libraries.DateTime import convert_time
|
|
11
|
+
from robot.api.deco import not_keyword
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@contextmanager
|
|
15
|
+
def redirect(conn):
|
|
16
|
+
"""
|
|
17
|
+
Redirects the other party's ``stdout`` and ``stderr`` to local
|
|
18
|
+
"""
|
|
19
|
+
# pylint: disable=W0212
|
|
20
|
+
alreadyredirected, conn._is_redirected = conn._is_redirected, True
|
|
21
|
+
if not alreadyredirected and conn._is_connected:
|
|
22
|
+
# pylint: enable=W0212
|
|
23
|
+
orig_stdout = conn.root.stdout
|
|
24
|
+
orig_stderr = conn.root.stderr
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
conn.root.stdout = sys.stdout
|
|
28
|
+
conn.root.stderr = sys.stderr
|
|
29
|
+
yield
|
|
30
|
+
finally:
|
|
31
|
+
try:
|
|
32
|
+
conn.root.stdout = orig_stdout
|
|
33
|
+
conn.root.stderr = orig_stderr
|
|
34
|
+
except EOFError:
|
|
35
|
+
pass
|
|
36
|
+
# pylint: disable=W0212
|
|
37
|
+
conn._is_redirected = False
|
|
38
|
+
# pylint: enable=W0212
|
|
39
|
+
else:
|
|
40
|
+
yield
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def redirect_output(func: Callable):
|
|
44
|
+
"""
|
|
45
|
+
decorator for RPyC connetion to automatically forward
|
|
46
|
+
stdout and stderr from remote to local
|
|
47
|
+
"""
|
|
48
|
+
this = getattr(func, '__self__', None)
|
|
49
|
+
function = getattr(func, '__func__', func)
|
|
50
|
+
|
|
51
|
+
@functools.wraps(function)
|
|
52
|
+
def sync_request(self, handler, *args, **kwargs):
|
|
53
|
+
with redirect(self):
|
|
54
|
+
return function(self, handler, *args, **kwargs)
|
|
55
|
+
|
|
56
|
+
if this:
|
|
57
|
+
# pylint: disable=E1120
|
|
58
|
+
return sync_request.__get__(this, func.__class__)
|
|
59
|
+
# pylint: enable=E1120
|
|
60
|
+
return sync_request
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RPyCRobotRemoteClient:
|
|
64
|
+
"""
|
|
65
|
+
Implements Remote Client Interface for Robot Framework based on RPyC
|
|
66
|
+
"""
|
|
67
|
+
ROBOT_LIBRARY_SCOPE = 'GLOBAL'
|
|
68
|
+
|
|
69
|
+
__slot__ = ()
|
|
70
|
+
|
|
71
|
+
# pylint: disable=R0913
|
|
72
|
+
def __init__(self,
|
|
73
|
+
peer: str = 'localhost',
|
|
74
|
+
port: int = 18861,
|
|
75
|
+
ipv6: bool = False,
|
|
76
|
+
timeout=None,
|
|
77
|
+
logger=None,
|
|
78
|
+
**rpyc_config):
|
|
79
|
+
self._keywords_cache = None
|
|
80
|
+
|
|
81
|
+
if logger is None:
|
|
82
|
+
logger = logging.getLogger('RPyCRobotRemote.Client')
|
|
83
|
+
|
|
84
|
+
# pylint: disable=duplicate-code
|
|
85
|
+
config = {}
|
|
86
|
+
if rpyc_config:
|
|
87
|
+
config.update(rpyc_config)
|
|
88
|
+
|
|
89
|
+
config.update(
|
|
90
|
+
{
|
|
91
|
+
'allow_all_attrs': True,
|
|
92
|
+
'allow_getattr': True,
|
|
93
|
+
'allow_setattr': True,
|
|
94
|
+
'allow_delattr': True,
|
|
95
|
+
'allow_exposed_attrs': False,
|
|
96
|
+
'logger': logger,
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if timeout is not None:
|
|
101
|
+
config['sync_request_timeout'] = convert_time(
|
|
102
|
+
timeout,
|
|
103
|
+
result_format='number'
|
|
104
|
+
)
|
|
105
|
+
# pylint: enable=duplicate-code
|
|
106
|
+
|
|
107
|
+
self._client = rpyc.connect(
|
|
108
|
+
peer,
|
|
109
|
+
port,
|
|
110
|
+
config=config,
|
|
111
|
+
ipv6=ipv6,
|
|
112
|
+
keepalive=True,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# automatic redirect stdout + stderr from remote during
|
|
116
|
+
# during handling of sync_request
|
|
117
|
+
self._client._is_connected = True
|
|
118
|
+
self._client._is_redirected = False
|
|
119
|
+
self._client.sync_request = redirect_output(self._client.sync_request)
|
|
120
|
+
# pylint: enable=R0913
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def __doc__(self):
|
|
124
|
+
return getattr(self._client.root.library, '__doc__')
|
|
125
|
+
|
|
126
|
+
def __getattr__(self, name: str):
|
|
127
|
+
if (name[0:1] != '_' and
|
|
128
|
+
(not name.startswith('ROBOT_LIBRARY_') or
|
|
129
|
+
self._client._is_connected)):
|
|
130
|
+
try:
|
|
131
|
+
obj = getattr(self._client.root.library, name)
|
|
132
|
+
except AttributeError:
|
|
133
|
+
pass
|
|
134
|
+
else:
|
|
135
|
+
if name.startswith('ROBOT_LIBRARY_') or callable(obj):
|
|
136
|
+
return obj
|
|
137
|
+
raise AttributeError(
|
|
138
|
+
f'{type(self).__name__!r} object has no attribute {name!r}'
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def stop_remote_server(self):
|
|
142
|
+
"""Stop remote server."""
|
|
143
|
+
self._client.root.stop_remote_server()
|
|
144
|
+
# pylint: disable=W0212
|
|
145
|
+
self._client._is_connected = False
|
|
146
|
+
# pylint: enable=W0212
|
|
147
|
+
self._client.close()
|
|
148
|
+
|
|
149
|
+
@not_keyword
|
|
150
|
+
def get_keyword_names(self):
|
|
151
|
+
"""Return keyword names supported by the remote server."""
|
|
152
|
+
if self._keywords_cache is None:
|
|
153
|
+
base = set(self._client.root.get_keyword_names())
|
|
154
|
+
attributes = [(name, getattr(self, name))
|
|
155
|
+
for name in dir(self) if name[0:1] != '_']
|
|
156
|
+
self._keywords_cache = tuple(
|
|
157
|
+
sorted(
|
|
158
|
+
base | {
|
|
159
|
+
name for name, value in attributes
|
|
160
|
+
if (callable(value) and
|
|
161
|
+
not getattr(value, 'robot_not_keyword', False))
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
)
|
|
165
|
+
return self._keywords_cache
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Server Implementation for RPyCRobotRemote
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import pathlib
|
|
6
|
+
import logging
|
|
7
|
+
import io
|
|
8
|
+
import inspect
|
|
9
|
+
from typing import TextIO, Optional, Union
|
|
10
|
+
from robot.libraries.DateTime import convert_time
|
|
11
|
+
import rpyc
|
|
12
|
+
from rpyc.utils.server import ThreadedServer
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RPyCRobotRemoteServer:
|
|
16
|
+
"""
|
|
17
|
+
Implements Remote Sever Interface for Robot Framework based on RPyC
|
|
18
|
+
"""
|
|
19
|
+
# pylint: disable=R0913
|
|
20
|
+
def __init__(self, # noqa, C901 allow higher complexity here
|
|
21
|
+
library,
|
|
22
|
+
host: Optional[str] = 'localhost',
|
|
23
|
+
port: int = 18861,
|
|
24
|
+
port_file: Optional[Union[str, pathlib.Path, TextIO]] = None,
|
|
25
|
+
serve: bool = True,
|
|
26
|
+
allow_remote_stop: bool = True,
|
|
27
|
+
ipv6: bool = False,
|
|
28
|
+
timeout=None,
|
|
29
|
+
logger=None,
|
|
30
|
+
server=None,
|
|
31
|
+
**rpyc_config):
|
|
32
|
+
"""Configure and start-up remote server.
|
|
33
|
+
|
|
34
|
+
:param library: Test library instance or module to host.
|
|
35
|
+
:param host: Address to listen. Use None to listen
|
|
36
|
+
to all available interfaces.
|
|
37
|
+
:param port: Port to listen. Use ``0`` to select a free port
|
|
38
|
+
automatically. Can be given as an integer or as
|
|
39
|
+
a string.
|
|
40
|
+
:param port_file: File to write the port that is used. ``None`` means
|
|
41
|
+
no such file is written. Port file is created after
|
|
42
|
+
the server is started and removed automatically
|
|
43
|
+
after it has stopped.
|
|
44
|
+
:param serve: If ``True``, start the server automatically and
|
|
45
|
+
wait for it to be stopped.
|
|
46
|
+
:param allow_remote_stop: Allow/disallow stopping the server using
|
|
47
|
+
``Stop Remote Server`` keyword and
|
|
48
|
+
``stop_remote_server`` method.
|
|
49
|
+
:param ipv6 If ``True``, allow IPv6 connections,
|
|
50
|
+
if ``False``, use IPv4 only connections.
|
|
51
|
+
"""
|
|
52
|
+
class Service(rpyc.Service):
|
|
53
|
+
"""The root service provided"""
|
|
54
|
+
def __init__(self, library):
|
|
55
|
+
super().__init__()
|
|
56
|
+
self._library = library
|
|
57
|
+
|
|
58
|
+
if allow_remote_stop:
|
|
59
|
+
@staticmethod
|
|
60
|
+
def stop_remote_server():
|
|
61
|
+
"""stop server from remote"""
|
|
62
|
+
self.stop()
|
|
63
|
+
|
|
64
|
+
def get_keyword_names(self):
|
|
65
|
+
"""return the methods which can be used as keywords"""
|
|
66
|
+
get_kw_names = getattr(
|
|
67
|
+
self._library,
|
|
68
|
+
'get_keyword_names',
|
|
69
|
+
None
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if get_kw_names:
|
|
73
|
+
return tuple(sorted(set(get_kw_names())))
|
|
74
|
+
|
|
75
|
+
attributes = inspect.getmembers(
|
|
76
|
+
self._library,
|
|
77
|
+
is_function_or_method
|
|
78
|
+
)
|
|
79
|
+
return tuple(
|
|
80
|
+
name for name, value in attributes
|
|
81
|
+
if (name[0:1] != '_' and
|
|
82
|
+
not getattr(value, 'robot_not_keyword', False))
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def library(self):
|
|
87
|
+
"""wrapprt to retrieve the library object from remote"""
|
|
88
|
+
return self._library
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def stdin(self):
|
|
92
|
+
"""wrapprt to change stdout from remote"""
|
|
93
|
+
return sys.stdin
|
|
94
|
+
|
|
95
|
+
@stdin.setter
|
|
96
|
+
def stdin(self, value: TextIO):
|
|
97
|
+
sys.stdin = value
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def stdout(self):
|
|
101
|
+
"""wrapprt to change stdout from remote"""
|
|
102
|
+
return sys.stdout
|
|
103
|
+
|
|
104
|
+
@stdout.setter
|
|
105
|
+
def stdout(self, value: TextIO):
|
|
106
|
+
sys.stdout = value
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def stderr(self):
|
|
110
|
+
"""wrapprt to change stderr from remote"""
|
|
111
|
+
return sys.stderr
|
|
112
|
+
|
|
113
|
+
@stderr.setter
|
|
114
|
+
def stderr(self, value: TextIO):
|
|
115
|
+
sys.stderr = value
|
|
116
|
+
|
|
117
|
+
def _rpyc_setattr(self, name: str, value):
|
|
118
|
+
if name in ('stdin', 'stdout', 'stderr'):
|
|
119
|
+
return setattr(self, name, value)
|
|
120
|
+
return super()._rpyc_setattr(name, value)
|
|
121
|
+
|
|
122
|
+
self._port_file = (
|
|
123
|
+
port_file if (
|
|
124
|
+
port_file is None or isinstance(port_file, io.TextIOBase))
|
|
125
|
+
else pathlib.Path(port_file).absolute()
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if logger is None:
|
|
129
|
+
logger = logging.getLogger('RPyCRobotRemote.Server')
|
|
130
|
+
|
|
131
|
+
if server is None:
|
|
132
|
+
server = ThreadedServer
|
|
133
|
+
|
|
134
|
+
# pylint: disable=duplicate-code
|
|
135
|
+
config = {}
|
|
136
|
+
if rpyc_config:
|
|
137
|
+
config.update(rpyc_config)
|
|
138
|
+
|
|
139
|
+
config.update(
|
|
140
|
+
{
|
|
141
|
+
'allow_all_attrs': True,
|
|
142
|
+
'allow_getattr': True,
|
|
143
|
+
'allow_setattr': True,
|
|
144
|
+
'allow_delattr': True,
|
|
145
|
+
'allow_exposed_attrs': False,
|
|
146
|
+
'logger': logger,
|
|
147
|
+
}
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if timeout is not None:
|
|
151
|
+
config['sync_request_timeout'] = convert_time(
|
|
152
|
+
timeout,
|
|
153
|
+
result_format='number'
|
|
154
|
+
)
|
|
155
|
+
# pylint: enable=duplicate-code
|
|
156
|
+
|
|
157
|
+
self._server = server(
|
|
158
|
+
Service(library),
|
|
159
|
+
hostname=host,
|
|
160
|
+
port=port,
|
|
161
|
+
ipv6=ipv6,
|
|
162
|
+
auto_register=False,
|
|
163
|
+
logger=logger,
|
|
164
|
+
protocol_config=config,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
if serve:
|
|
168
|
+
self.serve()
|
|
169
|
+
# pylint: enable=R0913
|
|
170
|
+
|
|
171
|
+
def stop(self):
|
|
172
|
+
"""stop serving requests"""
|
|
173
|
+
self._server.active = False
|
|
174
|
+
|
|
175
|
+
def serve(self):
|
|
176
|
+
"""start serving requests"""
|
|
177
|
+
if self._port_file:
|
|
178
|
+
if isinstance(self._port_file, io.TextIOBase):
|
|
179
|
+
print(self.server_port, file=self._port_file)
|
|
180
|
+
else:
|
|
181
|
+
with self._port_file.open('w', encoding='utf-8') as f:
|
|
182
|
+
print(self.server_port, file=f)
|
|
183
|
+
self._server.start()
|
|
184
|
+
if self._port_file and not isinstance(self._port_file, io.TextIOBase):
|
|
185
|
+
self._port_file.unlink()
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def server_address(self):
|
|
189
|
+
"""Server address as a tuple ``(host, port)``."""
|
|
190
|
+
return self._server.host
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def server_port(self):
|
|
194
|
+
"""Server port as an integer.
|
|
195
|
+
|
|
196
|
+
If the initial given port is 0, also this property returns 0 until
|
|
197
|
+
the server is activated.
|
|
198
|
+
"""
|
|
199
|
+
return self._server.port
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def is_function_or_method(item):
|
|
203
|
+
"""return True in case item is a function or method"""
|
|
204
|
+
return inspect.isfunction(item) or inspect.ismethod(item)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
__init__.py for RPyCRobotRemote
|
|
3
|
+
"""
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
from collections import UserString
|
|
6
|
+
from io import IOBase
|
|
7
|
+
from rpyc.utils.server import Server as _RPyCServer
|
|
8
|
+
import robot.utils
|
|
9
|
+
import robot.variables.replacer
|
|
10
|
+
import robot.variables.assigner
|
|
11
|
+
import robot.variables.store
|
|
12
|
+
from .RPyCRobotRemoteClient import RPyCRobotRemoteClient as Client
|
|
13
|
+
from .RPyCRobotRemoteServer import RPyCRobotRemoteServer as Server # noqa, F401
|
|
14
|
+
|
|
15
|
+
RPyCRobotRemote = Client
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SingleServer(_RPyCServer):
|
|
19
|
+
"""
|
|
20
|
+
A server that handles a single connection (blockingly)
|
|
21
|
+
|
|
22
|
+
Parameters: see :class:`rpyc.utils.server.Server`
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def _accept_method(self, sock):
|
|
26
|
+
"""accept method"""
|
|
27
|
+
try:
|
|
28
|
+
self._authenticate_and_serve_client(sock)
|
|
29
|
+
finally:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# work around problems with is_list_like and remote tuples objects
|
|
34
|
+
# Python seems to have a problem with isinstance here
|
|
35
|
+
# and throws expcetion
|
|
36
|
+
def patch_is_list_like(func):
|
|
37
|
+
"""patch is_list_like as it leads to exception with remote namedtuples"""
|
|
38
|
+
def is_list_like(item):
|
|
39
|
+
for t in (str, bytes, bytearray, UserString, IOBase):
|
|
40
|
+
try:
|
|
41
|
+
if isinstance(item, t):
|
|
42
|
+
return False
|
|
43
|
+
except TypeError:
|
|
44
|
+
pass
|
|
45
|
+
try:
|
|
46
|
+
return isinstance(item, Iterable)
|
|
47
|
+
except TypeError:
|
|
48
|
+
pass
|
|
49
|
+
try:
|
|
50
|
+
iter(item)
|
|
51
|
+
except TypeError:
|
|
52
|
+
return False
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
func.__code__ = is_list_like.__code__
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
patch_is_list_like(robot.utils.is_list_like)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '0.0.1'
|
|
16
|
+
__version_tuple__ = version_tuple = (0, 0, 1)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ['setuptools>=45', 'setuptools_scm[toml]>=6.2']
|
|
3
|
+
|
|
4
|
+
[project]
|
|
5
|
+
name = 'robotframework-rpycremote'
|
|
6
|
+
authors = [
|
|
7
|
+
{name = 'René Lehfeld', email = '54720674+rlehfeld@users.noreply.github.com'},
|
|
8
|
+
]
|
|
9
|
+
description = 'Robot Framework Remote Library based on RPyC'
|
|
10
|
+
requires-python = '>=3.7'
|
|
11
|
+
license = {text = 'MIT'}
|
|
12
|
+
keywords = ['robotframework', 'robot-framework', 'rpyc']
|
|
13
|
+
classifiers = [
|
|
14
|
+
'Programming Language :: Python :: 3',
|
|
15
|
+
'Environment :: Other Environment',
|
|
16
|
+
'Intended Audience :: Developers',
|
|
17
|
+
'Operating System :: OS Independent',
|
|
18
|
+
'Topic :: Software Development :: Libraries :: Python Modules',
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
'robotframework>=5.0.1',
|
|
22
|
+
'rpyc>=6.0.0',
|
|
23
|
+
]
|
|
24
|
+
dynamic = ['version', 'readme']
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
'Homepage' = 'https://github.com/rlehfeld/robotframework-rpycremote'
|
|
28
|
+
'Bug Reports' = 'https://github.com/rlehfeld/robotframework-rpycremote/issues'
|
|
29
|
+
'Source' = 'https://github.com/rlehfeld/robotframework-rpycremote/'
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
packages = ['RPyCRobotRemote']
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.dynamic]
|
|
35
|
+
readme = {file = ['README.rst']}
|
|
36
|
+
|
|
37
|
+
[tool.setuptools_scm]
|
|
38
|
+
write_to = 'RPyCRobotRemote/_version.py'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: robotframework-rpycremote
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Robot Framework Remote Library based on RPyC
|
|
5
|
+
Author-email: René Lehfeld <54720674+rlehfeld@users.noreply.github.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/rlehfeld/robotframework-rpycremote
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/rlehfeld/robotframework-rpycremote/issues
|
|
9
|
+
Project-URL: Source, https://github.com/rlehfeld/robotframework-rpycremote/
|
|
10
|
+
Keywords: robotframework,robot-framework,rpyc
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Environment :: Other Environment
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.7
|
|
17
|
+
Description-Content-Type: text/x-rst
|
|
18
|
+
License-File: LICENSE.txt
|
|
19
|
+
Requires-Dist: robotframework>=5.0.1
|
|
20
|
+
Requires-Dist: rpyc>=6.0.0
|
|
21
|
+
|
|
22
|
+
RPyC Robot Remote Library
|
|
23
|
+
============================
|
|
24
|
+
Generic Robot Framework Remote library based on RPyC
|
|
25
|
+
|
|
26
|
+
Installation
|
|
27
|
+
============
|
|
28
|
+
Install the latest release via PyPi using pip:
|
|
29
|
+
|
|
30
|
+
::
|
|
31
|
+
|
|
32
|
+
pip install robotframework-rpycremote
|
|
33
|
+
|
|
34
|
+
Or add to your ``conda.yaml`` file:
|
|
35
|
+
|
|
36
|
+
::
|
|
37
|
+
|
|
38
|
+
- pip:
|
|
39
|
+
- robotframework-rpycremote
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
In oder to help with development you can directly install from GitHub via:
|
|
43
|
+
|
|
44
|
+
::
|
|
45
|
+
|
|
46
|
+
pip install git+https://github.com/rlehfeld/robotframework-rpycremote.git
|
|
47
|
+
|
|
48
|
+
Or add to your ``conda.yaml`` file:
|
|
49
|
+
|
|
50
|
+
::
|
|
51
|
+
|
|
52
|
+
- pip:
|
|
53
|
+
- git+https://github.com/rlehfeld/robotframework-rpycremote.git
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
Usage
|
|
57
|
+
=====
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
LICENSE.txt
|
|
3
|
+
README.rst
|
|
4
|
+
pyproject.toml
|
|
5
|
+
setup.py
|
|
6
|
+
.github/workflows/pipeline.yml
|
|
7
|
+
.github/workflows/publish-to-pypi.yml
|
|
8
|
+
RPyCRobotRemote/RPyCRobotRemoteClient.py
|
|
9
|
+
RPyCRobotRemote/RPyCRobotRemoteServer.py
|
|
10
|
+
RPyCRobotRemote/__init__.py
|
|
11
|
+
RPyCRobotRemote/_version.py
|
|
12
|
+
robotframework_rpycremote.egg-info/PKG-INFO
|
|
13
|
+
robotframework_rpycremote.egg-info/SOURCES.txt
|
|
14
|
+
robotframework_rpycremote.egg-info/dependency_links.txt
|
|
15
|
+
robotframework_rpycremote.egg-info/requires.txt
|
|
16
|
+
robotframework_rpycremote.egg-info/top_level.txt
|
|
17
|
+
test/client.py
|
|
18
|
+
test/provider.py
|
|
19
|
+
test/server.py
|
|
20
|
+
test/test.robot
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
RPyCRobotRemote
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test Code for RPyCRobotClient via python
|
|
3
|
+
"""
|
|
4
|
+
import inspect
|
|
5
|
+
import RPyCRobotRemote
|
|
6
|
+
|
|
7
|
+
conn = RPyCRobotRemote.Client()
|
|
8
|
+
print(conn.get_answer())
|
|
9
|
+
print(conn.dummy_test())
|
|
10
|
+
print(conn.get_region())
|
|
11
|
+
print(list(conn.get_region()))
|
|
12
|
+
print(conn.ROBOT_LIBRARY_DOC_FORMAT)
|
|
13
|
+
print(conn.__doc__)
|
|
14
|
+
print(conn.get_answer())
|
|
15
|
+
print(dir(conn))
|
|
16
|
+
|
|
17
|
+
print(inspect.getmembers(conn, callable))
|
|
18
|
+
|
|
19
|
+
conn.stop_remote_server()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sample service Provide() used for testing RPyCRobot client and server
|
|
3
|
+
"""
|
|
4
|
+
import numbers
|
|
5
|
+
from collections import namedtuple
|
|
6
|
+
from robot.api.deco import keyword, not_keyword
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Region(namedtuple('Region', 'x y width height')):
|
|
10
|
+
"""
|
|
11
|
+
namedtuple to reproduce problem in list assignment
|
|
12
|
+
with robot framework
|
|
13
|
+
"""
|
|
14
|
+
def __new__(cls, *args):
|
|
15
|
+
if not all(isinstance(x, numbers.Integral) for x in args):
|
|
16
|
+
raise TypeError('all parameters must be of type int')
|
|
17
|
+
return super(Region, cls).__new__(cls, *args)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Provider:
|
|
21
|
+
"""dummy test implementation"""
|
|
22
|
+
ROBOT_LIBRARY_DOC_FORMAT = 'text'
|
|
23
|
+
|
|
24
|
+
the_real_answer_though = 43
|
|
25
|
+
|
|
26
|
+
class Dummy:
|
|
27
|
+
"""dummy class"""
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self._value = 1
|
|
30
|
+
self.value2 = 5
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def value(self):
|
|
34
|
+
"""value getter"""
|
|
35
|
+
print('called value getter')
|
|
36
|
+
return self._value
|
|
37
|
+
|
|
38
|
+
@value.setter
|
|
39
|
+
def value(self, v):
|
|
40
|
+
print('called value setter')
|
|
41
|
+
self._value = v
|
|
42
|
+
|
|
43
|
+
def __call__(self, *args, **kwargs):
|
|
44
|
+
"""callable object"""
|
|
45
|
+
print(f'called __call__({args}, {kwargs})')
|
|
46
|
+
return '__call__'
|
|
47
|
+
|
|
48
|
+
def method(self, *args, **kwargs):
|
|
49
|
+
"""and some callable method"""
|
|
50
|
+
print(f'called method({args}, {kwargs})')
|
|
51
|
+
return 'method'
|
|
52
|
+
|
|
53
|
+
dummy_dict = {
|
|
54
|
+
'key': 'value'
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
def __init__(self):
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@not_keyword
|
|
61
|
+
def help_method(self):
|
|
62
|
+
"""help_method sample non keyword"""
|
|
63
|
+
print('should not be existing')
|
|
64
|
+
|
|
65
|
+
@keyword(name='Use Other Name')
|
|
66
|
+
def renamed_keyword(self):
|
|
67
|
+
"""sample renmaed keyword"""
|
|
68
|
+
print('via different name')
|
|
69
|
+
|
|
70
|
+
def get_answer(self, a=42, b: int = 56, /, *args, c: int = 59):
|
|
71
|
+
"""keyword which requires different arguments and return something"""
|
|
72
|
+
print(f'from remote {b} {args=}')
|
|
73
|
+
return a, c
|
|
74
|
+
|
|
75
|
+
def get_region(self):
|
|
76
|
+
"""keyword which return a region"""
|
|
77
|
+
return Region(1, 2, 3, 4)
|
|
78
|
+
|
|
79
|
+
def raise_error(self):
|
|
80
|
+
"""keyword which raises an error"""
|
|
81
|
+
raise RuntimeError('error')
|
|
82
|
+
|
|
83
|
+
def get_question(self):
|
|
84
|
+
"""keyword which returns a string"""
|
|
85
|
+
return "what is the airspeed velocity of an unladen swallow?"
|
|
86
|
+
|
|
87
|
+
def dummy_test(self):
|
|
88
|
+
"""keyword which returns an object"""
|
|
89
|
+
return self.Dummy()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test Code for RPyCRobotServer
|
|
3
|
+
"""
|
|
4
|
+
import sys
|
|
5
|
+
import logging
|
|
6
|
+
import logging.config
|
|
7
|
+
import yaml
|
|
8
|
+
from provider import Provider
|
|
9
|
+
import RPyCRobotRemote
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
LOGCONFIG = """
|
|
13
|
+
version: 1
|
|
14
|
+
formatters:
|
|
15
|
+
simple:
|
|
16
|
+
format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
17
|
+
handlers:
|
|
18
|
+
console:
|
|
19
|
+
class: logging.StreamHandler
|
|
20
|
+
level: DEBUG
|
|
21
|
+
formatter: simple
|
|
22
|
+
stream: ext://sys.__stderr__
|
|
23
|
+
loggers:
|
|
24
|
+
RPyCRobotRemote:
|
|
25
|
+
level: INFO
|
|
26
|
+
handlers: [console]
|
|
27
|
+
propagate: no
|
|
28
|
+
root:
|
|
29
|
+
level: DEBUG
|
|
30
|
+
handlers: [console]
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
logging.config.dictConfig(
|
|
34
|
+
yaml.load(
|
|
35
|
+
LOGCONFIG,
|
|
36
|
+
Loader=yaml.SafeLoader
|
|
37
|
+
),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
server = RPyCRobotRemote.Server(
|
|
41
|
+
Provider(),
|
|
42
|
+
serve=False,
|
|
43
|
+
# port=0,
|
|
44
|
+
port_file=sys.stdout,
|
|
45
|
+
server=RPyCRobotRemote.SingleServer
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
server.serve()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
*** Settings ***
|
|
2
|
+
Library RPyCRobotRemote localhost 18861 timeout=10 min WITH NAME RPyCTest
|
|
3
|
+
|
|
4
|
+
*** Test Cases ***
|
|
5
|
+
Test Remote
|
|
6
|
+
RPyCTest.Get Answer
|
|
7
|
+
RPyCTest.Use Other Name
|
|
8
|
+
|
|
9
|
+
${obj} = RPyCTest.Dummy Test
|
|
10
|
+
Log ${obj.value}
|
|
11
|
+
${ret} = Call Method ${obj} method 1 ${2} 3 key=${5}
|
|
12
|
+
${ret} = Call Method ${obj} __call__ 1 ${2} 3 key=${None}
|
|
13
|
+
${obj.value} Set Variable ${5}
|
|
14
|
+
Log ${obj.value2}
|
|
15
|
+
${obj.value2} Set Variable ${10}
|
|
16
|
+
|
|
17
|
+
Test Region Scalar
|
|
18
|
+
${region} RPyCTest.Get Region
|
|
19
|
+
|
|
20
|
+
Test Region List
|
|
21
|
+
${region} RPyCTest.Get Region
|
|
22
|
+
@{region} Set Variable ${{tuple($region)}}
|
|
23
|
+
|
|
24
|
+
Test Region Array via Scalar
|
|
25
|
+
${region} RPyCTest.Get Region
|
|
26
|
+
Log Many @{region}
|
|
27
|
+
|
|
28
|
+
Test Region Array
|
|
29
|
+
@{region} RPyCTest.Get Region
|
|
30
|
+
|
|
31
|
+
Test Exception
|
|
32
|
+
Run Keyword And Expect Error * RPyCTest.Raise Error
|
|
33
|
+
|
|
34
|
+
Test Stop Server
|
|
35
|
+
RPyCTest.Stop Remote Server
|