Skip to content

Commit 1da8e5e

Browse files
authored
Merge pull request #593 from altendky/test_windows
Test Windows and macOS
2 parents 5c57d19 + 45f7f47 commit 1da8e5e

10 files changed

+203
-89
lines changed

.github/workflows/ci.yml

+59-11
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ jobs:
2020
name: ${{ matrix.task.name }} - ${{ matrix.os.name }} ${{ matrix.python.name }} ${{ matrix.arch.name }}
2121
runs-on: ${{ matrix.os.runs-on }}
2222
container: ${{ matrix.os.container[matrix.python.docker] }}
23+
# present runtime seems to be about 1 minute 30 seconds
24+
timeout-minutes: 10
2325
strategy:
2426
fail-fast: false
2527
matrix:
@@ -30,7 +32,6 @@ jobs:
3032
os:
3133
- name: Linux
3234
runs-on: ubuntu-latest
33-
python_platform: linux
3435
matrix: linux
3536
container:
3637
2.7: docker://python:2.7-buster
@@ -40,55 +41,67 @@ jobs:
4041
3.9: docker://python:3.9-buster
4142
pypy2: docker://pypy:2-jessie
4243
pypy3: docker://pypy:3-stretch
43-
# - name: Windows
44-
# runs-on: windows-latest
45-
# python_platform: win32
46-
# matrix: windows
47-
# - name: macOS
48-
# runs-on: macos-latest
49-
# python_platform: darwin
50-
# matrix: macos
44+
- name: macOS
45+
runs-on: macos-latest
46+
matrix: macos
47+
- name: Windows
48+
runs-on: windows-latest
49+
matrix: windows
50+
openssl:
51+
x86: win32
52+
x64: win64
5153
python:
5254
- name: CPython 2.7
5355
tox: py27
5456
action: 2.7
5557
docker: 2.7
58+
matrix: 2.7
5659
implementation: cpython
5760
- name: PyPy 2.7
5861
tox: pypy27
5962
action: pypy-2.7
6063
docker: pypy2.7
64+
matrix: 2.7
6165
implementation: pypy
66+
openssl_msvc_version: 2019
6267
- name: CPython 3.6
6368
tox: py36
6469
action: 3.6
6570
docker: 3.6
71+
matrix: 3.6
6672
implementation: cpython
6773
- name: CPython 3.7
6874
tox: py37
6975
action: 3.7
7076
docker: 3.7
77+
matrix: 3.7
7178
implementation: cpython
7279
- name: CPython 3.8
7380
tox: py38
7481
action: 3.8
7582
docker: 3.8
83+
matrix: 3.8
7684
implementation: cpython
7785
- name: CPython 3.9
7886
tox: py39
7987
action: 3.9
8088
docker: 3.9
89+
matrix: 3.9
8190
implementation: cpython
8291
- name: PyPy 3.6
8392
tox: pypy36
8493
action: pypy-3.6
8594
docker: pypy3.6
95+
matrix: 3.6
8696
implementation: pypy
97+
openssl_msvc_version: 2019
8798
- name: PyPy 3.7
8899
tox: pypy37
89100
action: pypy-3.7
90101
docker: pypy3.7
102+
matrix: 3.7
91103
implementation: pypy
104+
openssl_msvc_version: 2019
92105
arch:
93106
- name: x86
94107
action: x86
@@ -105,6 +118,12 @@ jobs:
105118
matrix: macos
106119
arch:
107120
matrix: x86
121+
- os:
122+
matrix: windows
123+
python:
124+
implementation: pypy
125+
arch:
126+
matrix: x64
108127
env:
109128
# Should match name above
110129
JOB_NAME: ${{ matrix.task.name }} - ${{ matrix.os.name }} ${{ matrix.python.name }} ${{ matrix.arch.name }}
@@ -129,7 +148,37 @@ jobs:
129148
pip install --upgrade pip setuptools wheel
130149
pip install --upgrade tox
131150
- uses: twisted/python-info-action@v1.0.1
151+
- name: Add PyPy Externals
152+
if: ${{ matrix.os.matrix == 'windows' && matrix.python.implementation == 'pypy'}}
153+
env:
154+
PYPY_EXTERNALS_PATH: ${{ github.workspace }}/pypy_externals
155+
shell: bash
156+
run: |
157+
echo $PYPY_EXTERNALS_PATH
158+
mkdir --parents $(dirname $PYPY_EXTERNALS_PATH)
159+
hg clone https://foss.heptapod.net/pypy/externals/ $PYPY_EXTERNALS_PATH
160+
dir $PYPY_EXTERNALS_PATH
161+
cd $PYPY_EXTERNALS_PATH && hg update win32_14x
162+
echo "INCLUDE=$PYPY_EXTERNALS_PATH/include;$INCLUDE" >> $GITHUB_ENV
163+
echo "LIB=$PYPY_EXTERNALS_PATH/lib;$LIB" >> $GITHUB_ENV
164+
# echo "CL=${{ matrix.PYTHON.CL_FLAGS }}" >> $GITHUB_ENV
165+
- name: Add Brew
166+
if: ${{ matrix.os.matrix == 'macos' && matrix.python.implementation == 'pypy'}}
167+
shell: bash
168+
run: |
169+
brew install openssl@1.1 rust
170+
echo "LDFLAGS=-L$(brew --prefix openssl@1.1)/lib" >> $GITHUB_ENV
171+
echo "CFLAGS=-I$(brew --prefix openssl@1.1)/include" >> $GITHUB_ENV
172+
- name: rustup
173+
if: ${{ matrix.os.matrix == 'windows' && matrix.python.implementation == 'pypy'}}
174+
shell: bash
175+
run: |
176+
rustup target add i686-pc-windows-msvc
132177
- name: Test
178+
env:
179+
# When compiling Cryptography for PyPy on Windows there is a cleanup
180+
# failure. This is CI, it doesn't matter.
181+
PIP_NO_CLEAN: 1
133182
run: |
134183
tox -vv -e ${{ matrix.python.tox }}
135184
- name: Coverage Processing
@@ -161,7 +210,6 @@ jobs:
161210
os:
162211
- name: Linux
163212
runs-on: ubuntu-latest
164-
python_platform: linux
165213
matrix: linux
166214
container:
167215
3.8: docker://python:3.8-buster
@@ -195,6 +243,7 @@ jobs:
195243
# Should match JOB_NAME below
196244
name: ${{ matrix.task.name }} - ${{ matrix.os.name }} ${{ matrix.python.name }} ${{ matrix.arch.name }}
197245
runs-on: ${{ matrix.os.runs-on }}
246+
if: always()
198247
needs:
199248
- test
200249
container: ${{ matrix.os.container[matrix.python.docker] }}
@@ -208,7 +257,6 @@ jobs:
208257
os:
209258
- name: Linux
210259
runs-on: ubuntu-latest
211-
python_platform: linux
212260
matrix: linux
213261
container:
214262
3.8: docker://python:3.8-buster

pymodbus/server/sync.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -327,16 +327,17 @@ def __init__(self, context, framer=None, identity=None,
327327
self.control = ModbusControlBlock()
328328
self.address = address or ("", Defaults.Port)
329329
self.handler = handler or ModbusConnectedRequestHandler
330-
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves',
330+
self.ignore_missing_slaves = kwargs.pop('ignore_missing_slaves',
331331
Defaults.IgnoreMissingSlaves)
332-
self.broadcast_enable = kwargs.get('broadcast_enable',
332+
self.broadcast_enable = kwargs.pop('broadcast_enable',
333333
Defaults.broadcast_enable)
334334

335335
if isinstance(identity, ModbusDeviceIdentification):
336336
self.control.Identity.update(identity)
337337

338338
socketserver.ThreadingTCPServer.__init__(self, self.address,
339-
self.handler)
339+
self.handler,
340+
**kwargs)
340341

341342
def process_request(self, request, client):
342343
""" Callback for connecting a new client thread
@@ -456,16 +457,16 @@ def __init__(self, context, framer=None, identity=None, address=None,
456457
self.control = ModbusControlBlock()
457458
self.address = address or ("", Defaults.Port)
458459
self.handler = handler or ModbusDisconnectedRequestHandler
459-
self.ignore_missing_slaves = kwargs.get('ignore_missing_slaves',
460+
self.ignore_missing_slaves = kwargs.pop('ignore_missing_slaves',
460461
Defaults.IgnoreMissingSlaves)
461-
self.broadcast_enable = kwargs.get('broadcast_enable',
462+
self.broadcast_enable = kwargs.pop('broadcast_enable',
462463
Defaults.broadcast_enable)
463464

464465
if isinstance(identity, ModbusDeviceIdentification):
465466
self.control.Identity.update(identity)
466467

467468
socketserver.ThreadingUDPServer.__init__(self,
468-
self.address, self.handler)
469+
self.address, self.handler, **kwargs)
469470
# self._BaseServer__shutdown_request = True
470471

471472
def process_request(self, request, client):

requirements-tests.txt

+6-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ sqlalchemy>=1.1.15
1414
#wsgiref>=0.1.2
1515
verboselogs >= 1.5
1616
tornado==4.5.3
17-
Twisted[serial]>=20.3.0
17+
# using platform_python_implementation rather than
18+
# implementation_name for Python 2 support
19+
Twisted[conch,serial]>=20.3.0; platform_python_implementation != "PyPy" or sys_platform != "win32"
20+
# pywin32 isn't supported on pypy
21+
# https://github.com/mhammond/pywin32/issues/1289
22+
Twisted[conch]>=20.3.0; platform_python_implementation == "PyPy" and sys_platform == "win32"
1823
zope.interface>=4.4.0
1924
asynctest>=0.10.0

setup.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@
9797
'sphinx_rtd_theme',
9898
'humanfriendly'],
9999
'twisted': [
100-
'twisted[serial] >= 20.3.0',
101-
'pyasn1 >= 0.1.4',
100+
# using platform_python_implementation rather than
101+
# implementation_name for Python 2 support
102+
'Twisted[conch,serial]>=20.3.0; platform_python_implementation != "PyPy" or sys_platform != "win32"',
103+
# pywin32 isn't supported on pypy
104+
# https://github.com/mhammond/pywin32/issues/1289
105+
'Twisted[conch]>=20.3.0; platform_python_implementation == "PyPy" and sys_platform == "win32"',
102106
],
103107
'tornado': [
104108
'tornado == 4.5.3'

test/test_client_async.py

+60-38
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
#!/usr/bin/env python
2+
import contextlib
3+
import sys
24
import unittest
35
import pytest
46
from pymodbus.compat import IS_PYTHON3, PYTHON_VERSION
@@ -30,13 +32,18 @@
3032
import ssl
3133

3234
IS_DARWIN = platform.system().lower() == "darwin"
35+
IS_WINDOWS = platform.system().lower() == "windows"
3336
OSX_SIERRA = LooseVersion("10.12")
3437
if IS_DARWIN:
3538
IS_HIGH_SIERRA_OR_ABOVE = LooseVersion(platform.mac_ver()[0])
3639
SERIAL_PORT = '/dev/ttyp0' if not IS_HIGH_SIERRA_OR_ABOVE else '/dev/ptyp0'
3740
else:
3841
IS_HIGH_SIERRA_OR_ABOVE = False
39-
SERIAL_PORT = "/dev/ptmx"
42+
if IS_WINDOWS:
43+
# the use is mocked out
44+
SERIAL_PORT = ""
45+
else:
46+
SERIAL_PORT = "/dev/ptmx"
4047

4148
# ---------------------------------------------------------------------------#
4249
# Fixture
@@ -47,6 +54,15 @@ def mock_asyncio_gather(coro):
4754
return coro
4855

4956

57+
@contextlib.contextmanager
58+
def maybe_manage(condition, manager):
59+
if condition:
60+
with manager as value:
61+
yield value
62+
else:
63+
yield None
64+
65+
5066
class TestAsynchronousClient(object):
5167
"""
5268
This is the unittest for the pymodbus.client.asynchronous module
@@ -175,6 +191,10 @@ def testUdpAsycioClient(self, mock_gather, mock_event_loop):
175191
# Test Serial client
176192
# -----------------------------------------------------------------------#
177193

194+
@pytest.mark.skipif(
195+
sys.platform == 'win32' and platform.python_implementation() == 'PyPy',
196+
reason='Twisted serial requires pywin32 which is not compatible with PyPy',
197+
)
178198
@pytest.mark.parametrize("method, framer", [("rtu", ModbusRtuFramer),
179199
("socket", ModbusSocketFramer),
180200
("binary", ModbusBinaryFramer),
@@ -185,55 +205,57 @@ def testSerialTwistedClient(self, method, framer):
185205
with patch("serial.Serial") as mock_sp:
186206
from twisted.internet import reactor
187207
from twisted.internet.serialport import SerialPort
208+
with maybe_manage(sys.platform == 'win32', patch.object(SerialPort, "_finishPortSetup")):
209+
with patch('twisted.internet.reactor') as mock_reactor:
188210

189-
with patch('twisted.internet.reactor') as mock_reactor:
190-
191-
protocol, client = AsyncModbusSerialClient(schedulers.REACTOR,
192-
method=method,
193-
port=SERIAL_PORT,
194-
proto_cls=ModbusSerClientProtocol)
211+
protocol, client = AsyncModbusSerialClient(schedulers.REACTOR,
212+
method=method,
213+
port=SERIAL_PORT,
214+
proto_cls=ModbusSerClientProtocol)
195215

196-
assert (isinstance(client, SerialPort))
197-
assert (isinstance(client.protocol, ModbusSerClientProtocol))
198-
assert (0 == len(list(client.protocol.transaction)))
199-
assert (isinstance(client.protocol.framer, framer))
200-
assert (client.protocol._connected)
216+
assert (isinstance(client, SerialPort))
217+
assert (isinstance(client.protocol, ModbusSerClientProtocol))
218+
assert (0 == len(list(client.protocol.transaction)))
219+
assert (isinstance(client.protocol.framer, framer))
220+
assert (client.protocol._connected)
201221

202-
def handle_failure(failure):
203-
assert (isinstance(failure.exception(), ConnectionException))
222+
def handle_failure(failure):
223+
assert (isinstance(failure.exception(), ConnectionException))
204224

205-
d = client.protocol._buildResponse(0x00)
206-
d.addCallback(handle_failure)
225+
d = client.protocol._buildResponse(0x00)
226+
d.addCallback(handle_failure)
207227

208-
assert (client.protocol._connected)
209-
client.protocol.close()
210-
protocol.stop()
211-
assert (not client.protocol._connected)
228+
assert (client.protocol._connected)
229+
client.protocol.close()
230+
protocol.stop()
231+
assert (not client.protocol._connected)
212232

213233
@pytest.mark.parametrize("method, framer", [("rtu", ModbusRtuFramer),
214234
("socket", ModbusSocketFramer),
215235
("binary", ModbusBinaryFramer),
216236
("ascii", ModbusAsciiFramer)])
217237
def testSerialTornadoClient(self, method, framer):
218238
""" Test the serial tornado client client initialize """
219-
protocol, future = AsyncModbusSerialClient(schedulers.IO_LOOP, method=method, port=SERIAL_PORT)
220-
client = future.result()
221-
assert(isinstance(client, AsyncTornadoModbusSerialClient))
222-
assert(0 == len(list(client.transaction)))
223-
assert(isinstance(client.framer, framer))
224-
assert(client.port == SERIAL_PORT)
225-
assert(client._connected)
226-
227-
def handle_failure(failure):
228-
assert(isinstance(failure.exception(), ConnectionException))
229-
230-
d = client._build_response(0x00)
231-
d.add_done_callback(handle_failure)
232-
233-
assert(client._connected)
234-
client.close()
235-
protocol.stop()
236-
assert(not client._connected)
239+
from serial import Serial
240+
with maybe_manage(sys.platform in ('darwin', 'win32'), patch.object(Serial, "open")):
241+
protocol, future = AsyncModbusSerialClient(schedulers.IO_LOOP, method=method, port=SERIAL_PORT)
242+
client = future.result()
243+
assert(isinstance(client, AsyncTornadoModbusSerialClient))
244+
assert(0 == len(list(client.transaction)))
245+
assert(isinstance(client.framer, framer))
246+
assert(client.port == SERIAL_PORT)
247+
assert(client._connected)
248+
249+
def handle_failure(failure):
250+
assert(isinstance(failure.exception(), ConnectionException))
251+
252+
d = client._build_response(0x00)
253+
d.add_done_callback(handle_failure)
254+
255+
assert(client._connected)
256+
client.close()
257+
protocol.stop()
258+
assert(not client._connected)
237259

238260
@pytest.mark.skipif(IS_PYTHON3 , reason="requires python2.7")
239261
def testSerialAsyncioClientPython2(self):

0 commit comments

Comments
 (0)