Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add as checks with tests #192

Merged
merged 15 commits into from
Oct 26, 2020
43 changes: 29 additions & 14 deletions snap7/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import logging
import re
from ctypes import c_int, c_char_p, byref, sizeof, c_uint16, c_int32, c_byte
from ctypes import c_int, c_char_p, byref, sizeof, c_uint16, c_int32, c_byte, c_ulong
from ctypes import c_void_p
from datetime import datetime

Expand Down Expand Up @@ -506,7 +506,7 @@ def as_db_read(self, db_number, start, size):
data = (type_ * size)()
result = (self._library.Cli_AsDBRead(self._pointer, db_number, start, size, byref(data)))
check_error(result, context="client")
return bytearray(data)
return data

def as_db_write(self, db_number, start, data):
"""
Expand Down Expand Up @@ -616,6 +616,33 @@ def set_plc_datetime(self, dt):

return self._library.Cli_SetPlcDateTime(self._pointer, byref(buffer))

def check_as_completion(self, p_value) -> bool:
"""
Method to check Status of an async request. Result contains if the check was successful,
not the data value itself
:param p_value: Pointer where result of this check shall be written.
:return: 0 - Job is done successfully
:return: 1 - Job is either pending or contains s7errors
"""
result = self._library.Cli_CheckAsCompletion(self._pointer, p_value)
check_error(result, context="client")
return result

def set_as_callback(self):
# Cli_SetAsCallback
raise NotImplementedError

def wait_as_completion(self, timeout: c_ulong) -> int:
"""
Snap7 Cli_WaitAsCompletion representative.
:param timeout: ms to wait for async job
:return: Result of request in int format
"""
# Cli_WaitAsCompletion
result = self._library.Cli_WaitAsCompletion(self._pointer, timeout)
check_error(result, context="client")
return result

def asebread(self):
# Cli_AsEBRead
raise NotImplementedError
Expand Down Expand Up @@ -668,10 +695,6 @@ def aswritearea(self):
# Cli_AsWriteArea
raise NotImplementedError

def checkascompletion(self):
# Cli_CheckAsCompletion
raise NotImplementedError

def copyramtorom(self):
# Cli_CopyRamToRom
raise NotImplementedError
Expand Down Expand Up @@ -764,10 +787,6 @@ def readszllist(self):
# Cli_ReadSZLList
raise NotImplementedError

def setascallback(self):
# Cli_SetAsCallback
raise NotImplementedError

def setparam(self):
# Cli_SetParam
raise NotImplementedError
Expand All @@ -788,10 +807,6 @@ def tmwrite(self):
# Cli_TMWrite
raise NotImplementedError

def waitascompletion(self):
# Cli_WaitAsCompletion
raise NotImplementedError

def writemultivars(self):
# Cli_WriteMultiVars
raise NotImplementedError
2 changes: 1 addition & 1 deletion snap7/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def check_error(code, context="client"):
check if the error code is set. If so, a Python log message is generated
and an error is raised.
"""
if code:
if code and code != 1:
error = error_text(code, context)
logger.error(error)
raise Snap7Exception(error)
Expand Down
104 changes: 88 additions & 16 deletions test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

class TestClient(unittest.TestCase):

process = None

@classmethod
def setUpClass(cls):
cls.process = Process(target=mainloop)
Expand Down Expand Up @@ -309,8 +311,9 @@ def test_as_ct_write(self):
def test_as_db_fill(self):
self.client.as_db_fill()

@unittest.skip("TODO: not yet fully implemented")
def test_as_db_get(self):
self.client.db_get(db_number=db_number)
self.client.as_db_get(db_number=db_number)

@unittest.skip("TODO: crash client: FATAL: exception not rethrown")
def test_as_db_read(self):
Expand All @@ -328,6 +331,7 @@ def test_as_db_write(self):
data = bytearray(size)
self.client.as_db_write(db_number=1, start=0, data=data)

@unittest.skip("TODO: not yet fully implemented")
def test_as_download(self):
data = bytearray(128)
self.client.as_download(block_num=-1, data=data)
Expand Down Expand Up @@ -421,6 +425,7 @@ def test_ab_write_with_byte_literal_does_not_throw(self):
finally:
self.client._library.Cli_ABWrite = original

@unittest.skip("TODO: not yet fully implemented")
def test_as_ab_write_with_byte_literal_does_not_throw(self):
mock_write = mock.MagicMock()
mock_write.return_value = None
Expand All @@ -437,6 +442,7 @@ def test_as_ab_write_with_byte_literal_does_not_throw(self):
finally:
self.client._library.Cli_AsABWrite = original

@unittest.skip("TODO: not yet fully implemented")
def test_as_db_write_with_byte_literal_does_not_throw(self):
mock_write = mock.MagicMock()
mock_write.return_value = None
Expand All @@ -451,6 +457,7 @@ def test_as_db_write_with_byte_literal_does_not_throw(self):
finally:
self.client._library.Cli_AsDBWrite = original

@unittest.skip("TODO: not yet fully implemented")
def test_as_download_with_byte_literal_does_not_throw(self):
mock_download = mock.MagicMock()
mock_download.return_value = None
Expand All @@ -474,6 +481,86 @@ def test_set_plc_datetime(self):
# Can't actual set datetime in emulated PLC, get_plc_datetime always returns system time.
# self.assertEqual(new_dt, self.client.get_plc_datetime())

def test_wait_as_completion_pass(self, timeout=10):
# Cli_WaitAsCompletion
# prepare Server with values
area = snap7.types.areas.DB
dbnumber = 1
size = 1
start = 1
data = bytearray(size)
self.client.write_area(area, dbnumber, start, data)
# start as_request and test
p_data = self.client.as_db_read(dbnumber, start, size)
try:
self.client.wait_as_completion(ctypes.c_ulong(timeout))
except Snap7Exception as s7_err:
if s7_err.args[0] == b'CLI : Job Timeout':
self.fail(f"Request was timeouted after {timeout} seconds - FAIL")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the try except can be removed here, the test will then just fail if an exception is raised, no reason to capture it and manually fail

else:
self.fail(f"Snap7Exception was thrown: {s7_err} - FAIL")
except BaseException as pyt_err:
self.fail(f"Exception was thrown: {pyt_err}")
self.assertEqual(bytearray(p_data), data)

def test_wait_as_completion_timeouted(self, timeout=0, tries=500):
# Cli_WaitAsCompletion
# prepare Server
area = snap7.types.areas.DB
dbnumber = 1
size = 1
start = 1
data = bytearray(size)
self.client.write_area(area, dbnumber, start, data)
# start as_request and wait for zero seconds to try trigger timeout
for i in range(tries):
self.client.as_db_read(dbnumber, start, size)
try:
self.client.wait_as_completion(ctypes.c_ulong(0))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the try except can be removed here, the test will then just fail if an exception is raised, no reason to capture it and manually fail

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method itself is raising an Snap7Exception in case of a timeout, which is the original behaviour of the snap7 version as well.
The goal of this test is to catch this Snap7Exception"CLI : Job Timeout" so that the behaviour of wait_as_completion(...) in case of a Job Timeout can be tested. It is possible that other Snap7Exceptions may be raised, so these have to be caught and checked.
I agree with the BaseException below in this case.

except Snap7Exception as s7_err:
if not s7_err.args[0] == b'CLI : Job Timeout':
self.fail(f"While waiting another error appeared: {s7_err}")
return
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you return here? shouldn't it just continue?

Copy link
Contributor Author

@swamper123 swamper123 Oct 26, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise I would stuck in the loop, testing the behaviour multiple times(which is unnecessarry) and then, if the loop would be done, the test would fail.

So if once a Job Timeout was raised correctly, the test shall stop here and pass (via return).

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, skimmed too quickly.

except BaseException as pyt_err:
self.fail(f"Exception was thrown: {pyt_err}")
self.skipTest(f"After {tries} tries, no timout could be envoked. Skip test.")
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't want to skip on timeout, just fail


def test_check_as_completion(self, timeout=5):
# Cli_CheckAsCompletion
check_status = ctypes.c_int(-1)
pending_checked = False
# preparing Server values
size = 40
start = 0
db = 1
data = bytearray(40)
try:
self.client.db_write(db_number=db, start=start, data=data)
except Snap7Exception as s7_err:
self.fail(f"Snap7Exception raised while preparing as_check_completion test: {s7_err}")
except BaseException as python_err:
self.fail(f"Other exception raised while preparing as_check_completion test: {python_err}")
# Execute test
p_data = self.client.as_db_read(db, start, size)
for i in range(10):
try:
self.client.check_as_completion(ctypes.byref(check_status))
if check_status.value == 0:
data_result = bytearray(p_data)
self.assertEqual(data_result, data)
break
pending_checked = True
time.sleep(1)
except Snap7Exception as s7_err:
self.fail(f"Snap7Exception raised: {s7_err}")
except BaseException as python_err:
self.fail(f"Other exception raised: {python_err}")
else:
self.fail(f"TimeoutError - Process pends for more than {timeout} seconds")
if pending_checked is False:
logging.warning("Pending was never reached, because Server was to fast,"
" but request to server was successfull.")

def test_asebread(self):
# Cli_AsEBRead
with self.assertRaises(NotImplementedError):
Expand Down Expand Up @@ -539,11 +626,6 @@ def test_aswritearea(self):
with self.assertRaises(NotImplementedError):
self.client.aswritearea()

def test_checkascompletion(self):
# Cli_CheckAsCompletion
with self.assertRaises(NotImplementedError):
self.client.checkascompletion()

def test_copyramtorom(self):
# Cli_CopyRamToRom
with self.assertRaises(NotImplementedError):
Expand Down Expand Up @@ -659,11 +741,6 @@ def test_readszllist(self):
with self.assertRaises(NotImplementedError):
self.client.readszllist()

def test_setascallback(self):
# Cli_SetAsCallback
with self.assertRaises(NotImplementedError):
self.client.setascallback()

def test_setparam(self):
# Cli_SetParam
with self.assertRaises(NotImplementedError):
Expand All @@ -689,11 +766,6 @@ def test_tmwrite(self):
with self.assertRaises(NotImplementedError):
self.client.tmwrite()

def test_waitascompletion(self):
# Cli_WaitAsCompletion
with self.assertRaises(NotImplementedError):
self.client.waitascompletion()

def test_writemultivars(self):
# Cli_WriteMultiVars
with self.assertRaises(NotImplementedError):
Expand Down