diff --git a/Mainframe3270/__init__.py b/Mainframe3270/__init__.py index 9aae65b..384816a 100644 --- a/Mainframe3270/__init__.py +++ b/Mainframe3270/__init__.py @@ -8,9 +8,14 @@ from robot.utils import ConnectionCache from robotlibcore import DynamicCore -from Mainframe3270.keywords import (AssertionKeywords, CommandKeywords, - ConnectionKeywords, ReadWriteKeywords, - ScreenshotKeywords, WaitAndTimeoutKeywords) +from Mainframe3270.keywords import ( + AssertionKeywords, + CommandKeywords, + ConnectionKeywords, + ReadWriteKeywords, + ScreenshotKeywords, + WaitAndTimeoutKeywords, +) from Mainframe3270.py3270 import Emulator from Mainframe3270.utils import convert_timeout from Mainframe3270.version import VERSION @@ -68,6 +73,46 @@ class Mainframe3270(DynamicCore): | Switch Connection second # switchting the ocnnection using the alias | Page Should Contain String Second String | [Teardown] Close All Connections + + = Changing the emulator model (experimental) = + + By default, the library uses the emulator model 2, which is 24 rows by 80 columns. + You can, however, change the model globally when `importing` the library with the `model` argument + set to the model of your choice. + + The basic models are 2, 3, 4, and 5. These models differ in their screen size as illustrated in this table: + + | *3270 Model* | *Rows* | *Columns* | + | 2 | 24 | 80 | + | 3 | 32 | 80 | + | 4 | 43 | 80 | + | 5 | 27 | 132 | + + + They can be combined with the 3278 (monochrome green-screen) or 3279 (color) prefix, e.g. 3278-2 or 3279-2. + + In addition to that, there is a -E suffix that indicates support for the [https://x3270.miraheze.org/wiki/3270_data_stream_protocol#extended|x3270 extended data stream]. + + You can find more information on emulator models on the [https://x3270.miraheze.org/wiki/3270_models|x3270 wiki]. + + In addition to setting the model globally, you can also set the model on the individual emulator basis by providing the model arguments to the `Open Connection` + or `Open Connection From Session File` keywords. + + Here is an example for setting the emulator in the Open Connection keyword: + + | Open Connection pub400.com extra_args=["-xrm", "*model: 4"] + + And this is how you would set the emulator model in the Open Connection From Session File keyword: + + | Open Connection From Session File /path/to/session/file + + Where the content of the session file would be + + | *hostname: pub400.com + | *model: 4 + + + Note that this is an experimental feature, so not all models might work as expected. """ ROBOT_LIBRARY_SCOPE = "TEST SUITE" @@ -81,6 +126,7 @@ def __init__( wait_time_after_write: timedelta = timedelta(seconds=0), img_folder: str = ".", run_on_failure_keyword: str = "Take Screenshot", + model: str = "2", ) -> None: """ By default the emulator visibility is set to visible=True. @@ -107,6 +153,7 @@ def __init__( self.img_folder = img_folder self._running_on_failure_keyword = False self.register_run_on_failure_keyword(run_on_failure_keyword) + self.model = model self.cache = ConnectionCache() # When generating the library documentation with libdoc, BuiltIn.get_variable_value throws # a RobotNotRunningError. Therefore, we catch it here to be able to generate the documentation. @@ -159,8 +206,6 @@ def run_on_failure(self) -> None: self._running_on_failure_keyword = True BuiltIn().run_keyword(self.run_on_failure_keyword) except Exception as error: - logger.warn( - f"Keyword '{self.run_on_failure_keyword}' could not be run on failure: {error}" - ) + logger.warn(f"Keyword '{self.run_on_failure_keyword}' could not be run on failure: {error}") finally: self._running_on_failure_keyword = False diff --git a/Mainframe3270/keywords/__init__.py b/Mainframe3270/keywords/__init__.py index abcccbf..83ce265 100644 --- a/Mainframe3270/keywords/__init__.py +++ b/Mainframe3270/keywords/__init__.py @@ -3,5 +3,4 @@ from Mainframe3270.keywords.connection import ConnectionKeywords # noqa: F401 from Mainframe3270.keywords.read_write import ReadWriteKeywords # noqa: F401 from Mainframe3270.keywords.screenshot import ScreenshotKeywords # noqa: F401 -from Mainframe3270.keywords.wait_and_timeout import \ - WaitAndTimeoutKeywords # noqa: F401 +from Mainframe3270.keywords.wait_and_timeout import WaitAndTimeoutKeywords # noqa: F401 diff --git a/Mainframe3270/keywords/assertions.py b/Mainframe3270/keywords/assertions.py index 42915aa..fda1937 100644 --- a/Mainframe3270/keywords/assertions.py +++ b/Mainframe3270/keywords/assertions.py @@ -1,8 +1,10 @@ import re from typing import List, Optional + from robot.api import logger from robot.api.deco import keyword from robot.utils import Matcher + from Mainframe3270.librarycomponent import LibraryComponent @@ -109,9 +111,7 @@ def page_should_not_contain_any_string( | Page Should Not Contain Any Strings | ${list_of_string} | ignore_case=True | | Page Should Not Contain Any Strings | ${list_of_string} | error_message=New error message | """ - self._compare_all_list_with_screen_text( - list_string, ignore_case, error_message, should_match=False - ) + self._compare_all_list_with_screen_text(list_string, ignore_case, error_message, should_match=False) @keyword("Page Should Contain All Strings") def page_should_contain_all_strings( @@ -132,9 +132,7 @@ def page_should_contain_all_strings( | Page Should Contain All Strings | ${list_of_string} | ignore_case=True | | Page Should Contain All Strings | ${list_of_string} | error_message=New error message | """ - self._compare_all_list_with_screen_text( - list_string, ignore_case, error_message, should_match=True - ) + self._compare_all_list_with_screen_text(list_string, ignore_case, error_message, should_match=True) @keyword("Page Should Not Contain All Strings") def page_should_not_contain_all_strings( diff --git a/Mainframe3270/keywords/commands.py b/Mainframe3270/keywords/commands.py index 3faef3b..cdcfdd1 100644 --- a/Mainframe3270/keywords/commands.py +++ b/Mainframe3270/keywords/commands.py @@ -1,6 +1,8 @@ import time from typing import Optional + from robot.api.deco import keyword + from Mainframe3270.librarycomponent import LibraryComponent diff --git a/Mainframe3270/keywords/connection.py b/Mainframe3270/keywords/connection.py index 0b7da56..f15ab92 100644 --- a/Mainframe3270/keywords/connection.py +++ b/Mainframe3270/keywords/connection.py @@ -3,8 +3,10 @@ import shlex from os import name as os_name from typing import List, Optional, Union + from robot.api import logger from robot.api.deco import keyword + from Mainframe3270.librarycomponent import LibraryComponent from Mainframe3270.py3270 import Emulator @@ -62,7 +64,8 @@ def open_connection( | Open Connection | Hostname | alias=my_first_connection | """ extra_args = self._process_args(extra_args) - connection = Emulator(self.visible, self.timeout, extra_args) + model = self._get_model_from_list_or_file(extra_args) + connection = Emulator(self.visible, self.timeout, extra_args, model or self.model) host_string = f"{LU}@{host}" if LU else host if self._port_in_extra_args(extra_args): if port != 23: @@ -93,6 +96,17 @@ def _process_args(args) -> list: processed_args.append(arg) return processed_args + @staticmethod + def _get_model_from_list_or_file(list_or_file): + pattern = re.compile(r"[wcxs3270.*]+model:\s*([327892345E-]+)") + match = None + if isinstance(list_or_file, list): + match = pattern.findall(str(list_or_file)) + elif isinstance(list_or_file, os.PathLike) or isinstance(list_or_file, str): + with open(list_or_file) as file: + match = pattern.findall(file.read()) + return None if not match else match[-1] + @staticmethod def _port_in_extra_args(args) -> bool: if not args: @@ -103,9 +117,7 @@ def _port_in_extra_args(args) -> bool: return False @keyword("Open Connection From Session File") - def open_connection_from_session_file( - self, session_file: os.PathLike, alias: Optional[str] = None - ) -> int: + def open_connection_from_session_file(self, session_file: os.PathLike, alias: Optional[str] = None) -> int: """Create a connection to an IBM3270 mainframe using a [https://x3270.miraheze.org/wiki/Session_file|session file]. @@ -134,12 +146,12 @@ def open_connection_from_session_file( """ self._check_session_file_extension(session_file) self._check_contains_hostname(session_file) - self._check_model(session_file) + model = self._get_model_from_list_or_file(session_file) if os_name == "nt" and self.visible: - connection = Emulator(self.visible, self.timeout) + connection = Emulator(self.visible, self.timeout, model=model or self.model) connection.connect(str(session_file)) else: - connection = Emulator(self.visible, self.timeout, [str(session_file)]) + connection = Emulator(self.visible, self.timeout, [str(session_file)], model or self.model) return self.cache.register(connection, alias) def _check_session_file_extension(self, session_file): @@ -169,23 +181,6 @@ def _check_contains_hostname(session_file): "wc3270.hostname: myhost.com\n" ) - @staticmethod - def _check_model(session_file): - with open(session_file) as file: - pattern = re.compile(r"[wcxs3270.*]+model:\s*([327892345E-]+)") - match = pattern.findall(file.read()) - if not match: - return - elif match[-1] == "2": - return - else: - raise ValueError( - f'Robot-Framework-Mainframe-3270-Library currently only supports model "2", ' - f'the model you specified in your session file was "{match[-1]}". ' - f'Please change it to "2", using either the session wizard if you are on Windows, ' - f'or by editing the model resource like this "*model: 2"' - ) - @keyword("Switch Connection") def switch_connection(self, alias_or_index: Union[str, int]): """Switch the current connection to the one identified by index or alias. Indices are returned from diff --git a/Mainframe3270/keywords/read_write.py b/Mainframe3270/keywords/read_write.py index 6ff36f6..5ca1b02 100644 --- a/Mainframe3270/keywords/read_write.py +++ b/Mainframe3270/keywords/read_write.py @@ -1,6 +1,8 @@ import time from typing import Any, Optional + from robot.api.deco import keyword + from Mainframe3270.librarycomponent import LibraryComponent diff --git a/Mainframe3270/keywords/screenshot.py b/Mainframe3270/keywords/screenshot.py index d93fe89..295a68f 100644 --- a/Mainframe3270/keywords/screenshot.py +++ b/Mainframe3270/keywords/screenshot.py @@ -1,7 +1,9 @@ import os import time + from robot.api import logger from robot.api.deco import keyword + from Mainframe3270.librarycomponent import LibraryComponent @@ -42,13 +44,10 @@ def take_screenshot(self, height: int = 410, width: int = 670, filename_prefix: """ extension = "html" filename_sufix = round(time.time() * 1000) - filepath = os.path.join( - self.img_folder, "%s_%s.%s" % (filename_prefix, filename_sufix, extension) - ) + filepath = os.path.join(self.img_folder, "%s_%s.%s" % (filename_prefix, filename_sufix, extension)) self.mf.save_screen(os.path.join(self.output_folder, filepath)) logger.write( - '' - % (filepath.replace("\\", "/"), height, width), + '' % (filepath.replace("\\", "/"), height, width), level="INFO", html=True, ) diff --git a/Mainframe3270/keywords/wait_and_timeout.py b/Mainframe3270/keywords/wait_and_timeout.py index ffe1a02..f42eb60 100644 --- a/Mainframe3270/keywords/wait_and_timeout.py +++ b/Mainframe3270/keywords/wait_and_timeout.py @@ -1,7 +1,9 @@ import time from datetime import timedelta + from robot.api.deco import keyword from robot.utils import secs_to_timestr + from Mainframe3270.librarycomponent import LibraryComponent from Mainframe3270.utils import convert_timeout @@ -75,9 +77,7 @@ def wait_field_detected(self) -> None: self.mf.wait_for_field() @keyword("Wait Until String") - def wait_until_string( - self, txt: str, timeout: timedelta = timedelta(seconds=5) - ) -> str: + def wait_until_string(self, txt: str, timeout: timedelta = timedelta(seconds=5)) -> str: """Wait until a string exists on the mainframe screen to perform the next step. If the string does not appear in 5 seconds, the keyword will raise an exception. You can define a different timeout. diff --git a/Mainframe3270/librarycomponent.py b/Mainframe3270/librarycomponent.py index 0900856..c714f6f 100644 --- a/Mainframe3270/librarycomponent.py +++ b/Mainframe3270/librarycomponent.py @@ -1,4 +1,5 @@ from robot.utils import ConnectionCache + from Mainframe3270.py3270 import Emulator @@ -66,3 +67,7 @@ def mf(self) -> Emulator: @property def output_folder(self): return self.library.output_folder + + @property + def model(self): + return self.library.model diff --git a/Mainframe3270/py3270.py b/Mainframe3270/py3270.py index 0d580ba..a7fac1f 100644 --- a/Mainframe3270/py3270.py +++ b/Mainframe3270/py3270.py @@ -8,6 +8,8 @@ from contextlib import closing from os import name as os_name +from robot.utils import seq2str + log = logging.getLogger(__name__) """ Python 3+ note: unicode strings should be used when communicating with the Emulator methods. @@ -78,9 +80,7 @@ def handle_result(self, result): if result == "ok": return if result != "error": - raise ValueError( - 'expected "ok" or "error" result, but received: {0}'.format(result) - ) + raise ValueError('expected "ok" or "error" result, but received: {0}'.format(result)) msg = b"[no error message]" if self.data: @@ -126,9 +126,8 @@ def executable(self): def args(self): pass - def __init__(self, extra_args=None): - if extra_args: - self.args = self.__class__.args + extra_args + def __init__(self, extra_args=None, model="2"): + self.args = self._get_executable_app_args(extra_args, model) self.sp = None self.spawn_app() @@ -155,6 +154,9 @@ def write(self, data): def readline(self): return self.sp.stdout.readline() + def _get_executable_app_args(self, extra_args, model): + return self.__class__.args + ["-xrm", f"*model: {model}"] + (extra_args or []) + class x3270App(ExecutableApp): executable = "x3270" @@ -163,7 +165,7 @@ class x3270App(ExecutableApp): # work around that, when AID commands are sent, there is a 350ms delay # before the command returns. This arg turns that feature off for # performance reasons. - args = ["-xrm", "x3270.unlockDelay: False", "-xrm", "x3270.model: 2", "-script"] + args = ["-xrm", "x3270.unlockDelay: False", "-script"] class s3270App(ExecutableApp): @@ -179,11 +181,10 @@ class NotConnectedException(Exception): class wc3270App(ExecutableApp): executable = "wc3270" # see notes for args in x3270App - args = ["-xrm", "wc3270.unlockDelay: False", "-xrm", "wc3270.model: 2"] + args = ["-xrm", "wc3270.unlockDelay: False"] - def __init__(self, extra_args=None): - if extra_args: - self.args = wc3270App.args + extra_args + def __init__(self, extra_args=None, model="2"): + self.args = self._get_executable_app_args(extra_args, model) self.sp = None self.socket_fh = None self.script_port = self._get_free_port() @@ -254,7 +255,49 @@ class Emulator(object): with it. """ - def __init__(self, visible=False, timeout=30, extra_args=None, app=None): + _MODEL_TYPES = { + "2": "2", + "3278-2": "2", + "3278-2-E": "2", + "3279-2": "2", + "3279-2-E": "2", + "3": "3", + "3278-3": "3", + "3278-3-E": "3", + "3279-3": "3", + "3279-3-E": "3", + "4": "4", + "3278-4": "4", + "3278-4-E": "4", + "3279-4": "4", + "3279-4-E": "4", + "5": "5", + "3278-5": "5", + "3278-5-E": "5", + "3279-5": "5", + "3279-5-E": "5", + } + + _MODEL_DIMENSIONS = { + "2": { + "rows": 24, + "columns": 80, + }, + "3": { + "rows": 32, + "columns": 80, + }, + "4": { + "rows": 43, + "columns": 80, + }, + "5": { + "rows": 27, + "columns": 132, + }, + } + + def __init__(self, visible=False, timeout=30, extra_args=None, model="2"): """ Create an emulator instance @@ -263,30 +306,32 @@ def __init__(self, visible=False, timeout=30, extra_args=None, app=None): to x3270. `extra_args` allows sending parameters to the emulator executable """ - self.app = app or self.create_app(visible, extra_args) + self.model = model + self.model_dimensions = self._set_model_dimensions(model) + self.app = self.create_app(visible, extra_args, model) self.is_terminated = False self.status = Status(None) self.timeout = timeout self.last_host = None - def __del__(self): - """ - Since an emulator creates a process (and sometimes a socket handle), it is good practice - to clean these up when done. Note, not terminating at this point will usually have no - ill effect - only Python 3+ on Windows had problems in this regard. - """ - # self.terminate() # The terminate function is no longer needed in python 3.8 - pass + def _set_model_dimensions(self, model): + try: + model_type = Emulator._MODEL_TYPES[model] + except KeyError: + raise ValueError( + f"Model should be one of {seq2str(Emulator._MODEL_TYPES.keys()).replace('and', 'or')}, " + f"but was '{model}'." + ) + return Emulator._MODEL_DIMENSIONS[model_type] - @staticmethod - def create_app(visible, extra_args): + def create_app(self, visible, extra_args, model): if os_name == "nt": if visible: - return wc3270App(extra_args) - return ws3270App(extra_args) + return wc3270App(extra_args, model) + return ws3270App(extra_args, model) if visible: - return x3270App(extra_args) - return s3270App(extra_args) + return x3270App(extra_args, model) + return s3270App(extra_args, model) def exec_command(self, cmdstr): """ @@ -376,9 +421,7 @@ def wait_for_field(self): self.exec_command("Wait({0}, InputField)".format(self.timeout).encode("utf-8")) if self.status.keyboard != b"U": raise KeyboardStateError( - "keyboard not unlocked, state was: {0}".format( - self.status.keyboard.decode("utf-8") - ) + "keyboard not unlocked, state was: {0}".format(self.status.keyboard.decode("utf-8")) ) def move_to(self, ypos, xpos): @@ -435,16 +478,12 @@ def string_get(self, ypos, xpos, length): terminal. """ self._check_limits(ypos, xpos) - if (xpos + length) > (80 + 1): - raise Exception( - "You have exceeded the x-axis limit of the mainframe screen" - ) + if (xpos + length) > (self.model_dimensions["columns"] + 1): + raise Exception("You have exceeded the x-axis limit of the mainframe screen") # the screen's coordinates are 1 based, but the command is 0 based xpos -= 1 ypos -= 1 - cmd = self.exec_command( - "ascii({0},{1},{2})".format(ypos, xpos, length).encode("utf-8") - ) + cmd = self.exec_command("ascii({0},{1},{2})".format(ypos, xpos, length).encode("utf-8")) # this usage of utf-8 should only return a single line of data assert len(cmd.data) == 1, cmd.data return cmd.data[0].decode("unicode_escape") @@ -453,8 +492,8 @@ def search_string(self, string, ignore_case=False): """ Check if a string exists on the mainframe screen and return True or False. """ - for ypos in range(24): - line = self.string_get(ypos + 1, 1, 80) + for ypos in range(self.model_dimensions["rows"]): + line = self.string_get(ypos + 1, 1, self.model_dimensions["columns"]) if ignore_case: line = line.lower() if string in line: @@ -466,8 +505,8 @@ def read_all_screen(self): Read all the mainframe screen and return it in a single string. """ full_text = "" - for ypos in range(24): - full_text += self.string_get(ypos + 1, 1, 80) + for ypos in range(self.model_dimensions["rows"]): + full_text += self.string_get(ypos + 1, 1, self.model_dimensions["columns"]) return full_text def delete_field(self): @@ -501,13 +540,8 @@ def fill_field(self, ypos, xpos, tosend, length): def save_screen(self, file_path): self.exec_command("PrintText(html,file,{0})".format(file_path).encode("utf-8")) - @staticmethod - def _check_limits(ypos, xpos): - if ypos > 24: - raise Exception( - "You have exceeded the y-axis limit of the mainframe screen" - ) - if xpos > 80: - raise Exception( - "You have exceeded the x-axis limit of the mainframe screen" - ) + def _check_limits(self, ypos, xpos): + if ypos > self.model_dimensions["rows"]: + raise Exception("You have exceeded the y-axis limit of the mainframe screen") + if xpos > self.model_dimensions["columns"]: + raise Exception("You have exceeded the x-axis limit of the mainframe screen") diff --git a/Mainframe3270/utils.py b/Mainframe3270/utils.py index 06bc29d..ccdd9d4 100644 --- a/Mainframe3270/utils.py +++ b/Mainframe3270/utils.py @@ -1,4 +1,5 @@ from datetime import timedelta + from robot.utils import timestr_to_secs diff --git a/atest/HelperLibrary.py b/atest/HelperLibrary.py new file mode 100644 index 0000000..ebca38d --- /dev/null +++ b/atest/HelperLibrary.py @@ -0,0 +1,43 @@ +import os +import time + +from robot.libraries.BuiltIn import BuiltIn, RobotNotRunningError + + +class HelperLibrary: + ROBOT_LISTENER_API_VERSION = 2 + + def __init__(self): + self.built_in = BuiltIn() + self.ROBOT_LIBRARY_LISTENER = self + try: + self.library = self.built_in.get_library_instance("Mainframe3270") + except RobotNotRunningError: + pass + + def create_session_file(self, *content_lines): + extensions = { + ("nt", True): "wc3270", + ("nt", False): "ws3270", + ("posix", True): "x3270", + ("posix", False): "s3270", + } + extension = extensions.get((os.name, self.library.visible)) + session_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources", f"session.{extension}") + with open(session_file, "w", encoding="utf-8") as file: + for line in content_lines: + file.write(line + "\n") + return session_file + + def emulator_model_should_be(self, model): + error_message = f'Emulator model should have been "{model}", but was "{self.library.mf.model}"' + self.built_in.should_be_equal_as_strings(model, self.library.mf.model, error_message, False) + + def _end_keyword(self, name, attributes): + if attributes["kwname"] in [ + "Open Connection", + "Open Connection From Session File", + "Close Connection", + "Close All Connections", + ]: + time.sleep(1.5) diff --git a/atest/connection.robot b/atest/connection.robot index 49e7787..a1ffca0 100644 --- a/atest/connection.robot +++ b/atest/connection.robot @@ -2,6 +2,7 @@ Resource pub400_variables.robot Library OperatingSystem Library ../Mainframe3270/ ${VISIBLE} +Library HelperLibrary.py Test Teardown Test Teardown @@ -16,27 +17,23 @@ ${TRACE_FILE} ${CURDIR}/x3270.trace Test Connection With Extra Args List ${extra_args}= Create List -port 992 -trace -tracefile ${TRACE_FILE} Open Connection L:${HOST} extra_args=${extra_args} - Sleep 0.5 s File Should Exist ${TRACE_FILE} Test Connection With Argfile Open Connection ${HOST} extra_args=${ARGFILE} - Sleep 0.5 s File Should Exist ${TRACE_FILE} Test Connection From Session File - ${session_file}= Create Session File + ${session_file}= Create Session File *hostname: L:pub400.com *port: 992 Open Connection From Session File ${SESSION_FILE} Wait Field Detected Page Should Contain String ${WELCOME} Test Concurrent Connections Open Connection ${HOST} alias=first - Sleep 0.5 s Write Bare ABCD Page Should Contain String ABCD Open Connection ${HOST} alias=second - Sleep 0.5 s Write Bare DEFG Page Should Contain String DEFG Page Should Not Contain String ABCD @@ -47,22 +44,6 @@ Test Concurrent Connections *** Keywords *** -Create Session File - ${os_name}= Evaluate os.name - IF '${os_name}' == 'nt' and ${VISIBLE} - ${session_file}= Set Variable ${CURDIR}/resources/session.wc3270 - ELSE IF '${os_name}' == 'nt' and not ${VISIBLE} - ${session_file}= Set Variable ${CURDIR}/resources/session.ws3270 - ELSE IF '${os_name}' == 'posix' and ${VISIBLE} - ${session_file}= Set Variable ${CURDIR}/resources/session.x3270 - ELSE IF '${os_name}' == 'posix' and not ${VISIBLE} - ${session_file}= Set Variable ${CURDIR}/resources/session.s3270 - END - Copy File ${SESSION_TEMPLATE} ${session_file} - # Using legacy [Return] for older RF versions - [Return] ${session_file} - Test Teardown Run Keyword And Ignore Error Close Connection - Sleep 1 second Remove File ${TRACE_FILE} diff --git a/atest/mainframe.robot b/atest/mainframe.robot index 7fb279c..6e19e02 100644 --- a/atest/mainframe.robot +++ b/atest/mainframe.robot @@ -1,9 +1,10 @@ *** Settings *** Documentation These tests verify that all keywords are working correctly and displaying the expected exception messages. -Library ../Mainframe3270/ run_on_failure_keyword=None Library OperatingSystem Library String +Library ../Mainframe3270/ run_on_failure_keyword=None +Library HelperLibrary.py Resource pub400_variables.robot Suite Setup Suite Setup @@ -80,12 +81,12 @@ Exception Test Page Should Contain Match Verify Pattern Not Found Page Should Contain Match ${STRING_NON_EXISTENT} ignore_case=${True} Exception Test Page Should Contain String X Times - Verify String Does Not Appear X Times Page Should Contain String X Times ${TEXT_TO_COUNT} 1 3 + Verify String Does Not Appear X Times Page Should Contain String X Times ${TEXT_TO_COUNT} 2 1 Verify String Does Not Appear X Times ... Page Should Contain String X Times ... ${TEXT_TO_COUNT_WRONG_CASE} - ... 1 - ... 5 + ... 3 + ... 2 ... ignore_case=${True} Exception Test Page Should Match Regex @@ -131,8 +132,8 @@ Test Page Should Contain Match Page Should Contain Match ${TEXT_MATCH_WRONG_CASE} ignore_case=${True} Test Page Should Contain String X Times - Page Should Contain String X Times ${TEXT_TO_COUNT} 3 - Page Should Contain String X Times ${TEXT_TO_COUNT_WRONG_CASE} 5 ignore_case=${True} + Page Should Contain String X Times ${TEXT_TO_COUNT} 1 + Page Should Contain String X Times ${TEXT_TO_COUNT_WRONG_CASE} 2 ignore_case=${True} Test Page Should Match Regex Page Should Match Regex ${VALID_REGEX} @@ -233,11 +234,9 @@ Suite Setup Set Screenshot Folder ${FOLDER} Change Wait Time 0.4 Change Wait Time After Write 0.4 - Sleep 3s Suite Teardown Run Keyword And Ignore Error Close Connection - Sleep 1s Verify String Not Found [Arguments] ${keyword} ${string} ${ignore_case}=${False} @@ -280,8 +279,8 @@ Verify String Does Not Appear X Times Run Keyword And Expect Error ... ${expected_error} ... ${keyword} - ... ${TEXT_TO_COUNT} - ... 1 + ... ${string} + ... ${wrong_number_of_times} ... ignore_case=${ignore_case} Verify String Found diff --git a/atest/models/model_on_import.robot b/atest/models/model_on_import.robot new file mode 100644 index 0000000..24d456c --- /dev/null +++ b/atest/models/model_on_import.robot @@ -0,0 +1,12 @@ +*** Settings *** +Library ../../Mainframe3270/ model=4 +Library ../HelperLibrary.py +Resource ../pub400_variables.robot + +Test Teardown Run Keyword And Ignore Error Close Connection + + +*** Test Cases *** +Should Use Model From Import + Open Connection ${HOST} + Emulator Model Should Be 4 diff --git a/atest/models/models.robot b/atest/models/models.robot new file mode 100644 index 0000000..544f663 --- /dev/null +++ b/atest/models/models.robot @@ -0,0 +1,28 @@ +*** Settings *** +Library ../../Mainframe3270/ +Library ../HelperLibrary.py +Resource ../pub400_variables.robot + +Test Teardown Run Keyword And Ignore Error Close All Connections + + +*** Test Cases *** +Model Should Default To 2 + Open Connection ${HOST} + Emulator Model Should Be 2 + +Open Connection Can Override Model + Open Connection ${HOST} extra_args=["-xrm", "*model: 4"] + Emulator Model Should Be 4 + +Open Connection From Session File Can Override Model + ${session_file}= Create Session File *hostname: ${HOST} *model: 5 + Open Connection From Session File ${session_file} + Emulator Model Should Be 5 + +Can Use Different Models In Different Sessions + Open Connection ${HOST} extra_args=["-xrm", "*model: 5"] + Emulator Model Should Be 5 + Sleep 0.5 s + Open Connection ${HOST} extra_args=["-xrm", "*model: 4"] + Emulator Model Should Be 4 diff --git a/atest/pub400_variables.robot b/atest/pub400_variables.robot index 664ca73..1e08469 100644 --- a/atest/pub400_variables.robot +++ b/atest/pub400_variables.robot @@ -11,7 +11,7 @@ ${WELCOME_TITLE} Welcome to PUB400.COM * your ${MAIN_MENU} IBM i Main Menu ${USER_TASK} User Tasks ${TEXT_MATCH} *PUB???.COM* -${TEXT_TO_COUNT} PUB400 +${TEXT_TO_COUNT} Server ${TEXT_NOT_MATCH} *PUB???400.COM* # Texts after write ${TEXT_AFTER_DELETE_CHAR} EST _ëçá @@ -21,7 +21,7 @@ ${TEXT_AFTER_MOVE_NEXT_FIELD} ${SPACE * 4} ${WELCOME_TITLE_WRONG_CASE} WELCOME TO PUB400.COM * YOUR PUBLIC IBM I SERVER ${WELCOME_WRONG_CASE} WELCOME TO PUB400.COM ${TEXT_MATCH_WRONG_CASE} *pub???.com* -${TEXT_TO_COUNT_WRONG_CASE} pub400 +${TEXT_TO_COUNT_WRONG_CASE} server ${TEXT_NOT_MATCH_WRONG_CASE} *pub???400.com* # Regex ${VALID_REGEX} PUB\\d{3} diff --git a/atest/resources/session.template b/atest/resources/session.template deleted file mode 100644 index 515e3c5..0000000 --- a/atest/resources/session.template +++ /dev/null @@ -1,2 +0,0 @@ -*hostname: L:pub400.com -*port: 992 diff --git a/atest/run_on_failure/custom_keyword_on_import.robot b/atest/run_on_failure/custom_keyword_on_import.robot index 5467ce4..697fc80 100644 --- a/atest/run_on_failure/custom_keyword_on_import.robot +++ b/atest/run_on_failure/custom_keyword_on_import.robot @@ -1,10 +1,11 @@ *** Settings *** -Library ../../Mainframe3270/ run_on_failure_keyword=Custom Run On Failure Keyword Library OperatingSystem +Library ../../Mainframe3270/ run_on_failure_keyword=Custom Run On Failure Keyword +Library ../HelperLibrary.py Resource ../pub400_variables.robot -Suite Setup Open Mainframe -Suite Teardown Close Mainframe +Suite Setup Open Connection ${HOST} +Suite Teardown Run Keyword And Ignore Error Close Connection *** Variables *** @@ -19,10 +20,6 @@ Should Run Custom Keyword *** Keywords *** -Open Mainframe - Open Connection ${HOST} - Sleep 3 seconds - Cause Error Run Keyword And Expect Error ... The string "${STRING_NON_EXISTENT}" was not found @@ -30,7 +27,3 @@ Cause Error Custom Run On Failure Keyword Create File ${CUSTOM_FILE} An error ocurred - -Close Mainframe - Run Keyword And Ignore Error Close Connection - Sleep 1 second diff --git a/atest/run_on_failure/none_on_import.robot b/atest/run_on_failure/none_on_import.robot index cf3e241..212f6bc 100644 --- a/atest/run_on_failure/none_on_import.robot +++ b/atest/run_on_failure/none_on_import.robot @@ -3,8 +3,8 @@ Library ../../Mainframe3270/ run_on_failure_keyword=None Library OperatingSystem Resource ../pub400_variables.robot -Suite Setup Open Mainframe -Suite Teardown Close Mainframe +Suite Setup Open Connection ${HOST} +Suite Teardown Run Keyword And Ignore Error Close Connection *** Test Cases *** @@ -14,15 +14,7 @@ None Should Run On Failure *** Keywords *** -Open Mainframe - Open Connection ${HOST} - Sleep 3 seconds - Cause Error Run Keyword And Expect Error ... The string "${STRING_NON_EXISTENT}" was not found ... Page Should Contain String ${STRING_NON_EXISTENT} - -Close Mainframe - Run Keyword And Ignore Error Close Connection - Sleep 1 second diff --git a/atest/run_on_failure/run_on_failure.robot b/atest/run_on_failure/run_on_failure.robot index 6c97af5..381bc0a 100644 --- a/atest/run_on_failure/run_on_failure.robot +++ b/atest/run_on_failure/run_on_failure.robot @@ -3,8 +3,8 @@ Library ../../Mainframe3270/ img_folder=${CURDIR} Library OperatingSystem Resource ../pub400_variables.robot -Suite Setup Open Mainframe -Suite Teardown Close Mainframe +Suite Setup Open Connection ${HOST} +Suite Teardown Run Keyword And Ignore Error Close Connection *** Variables *** @@ -30,10 +30,6 @@ Register None To Run On Failure *** Keywords *** -Open Mainframe - Open Connection ${HOST} - Sleep 3 seconds - Cause Error Run Keyword And Expect Error ... The string "${STRING_NON_EXISTENT}" was not found @@ -41,7 +37,3 @@ Cause Error Custom Run On Failure Keyword Create File ${CUSTOM_FILE} An error ocurred - -Close Mainframe - Run Keyword And Ignore Error Close Connection - Sleep 1 second diff --git a/mypy.ini b/mypy.ini deleted file mode 100644 index 976ba02..0000000 --- a/mypy.ini +++ /dev/null @@ -1,2 +0,0 @@ -[mypy] -ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7b3be37 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[tool.black] +line-length = 120 +target = ['py37'] + +[tool.isort] +profile = 'black' +line_length = 120 + +[tool.mypy] +ignore_missing_imports = true + +[tool.robotidy] +configure = [ + "ReplaceReturns:enabled=False" +] diff --git a/tasks.py b/tasks.py index 28a3bf8..a03a6c3 100644 --- a/tasks.py +++ b/tasks.py @@ -4,16 +4,18 @@ @task def lint_python(c): """Perform python code formatting with black, isort and flake8.""" - c.run("black ./setup.py ./tasks.py Mainframe3270/ utest/") - c.run("isort ./setup.py ./tasks.py Mainframe3270/ utest/") - c.run("flake8 ./setup.py ./tasks.py Mainframe3270/ utest/") + print("Linting python code with black, isort, flake8 and mypy...") + c.run("black ./setup.py ./tasks.py Mainframe3270/ atest/ utest/") + c.run("isort ./setup.py ./tasks.py Mainframe3270/ atest/ utest/") + c.run("flake8 ./setup.py ./tasks.py Mainframe3270/ atest/ utest/") c.run("mypy ./setup.py ./tasks.py Mainframe3270/") @task def lint_robot(c): """Perform robot code formatting with robotidy.""" - c.run("robotidy --configure ReplaceReturns:enabled=False atest/") + print("Lingting Robot Framework code with robotidy...") + c.run("robotidy atest/") @task(lint_python, lint_robot) diff --git a/utest/Mainframe3270/keywords/test_assertions.py b/utest/Mainframe3270/keywords/test_assertions.py index da7a1e6..e73c6e4 100644 --- a/utest/Mainframe3270/keywords/test_assertions.py +++ b/utest/Mainframe3270/keywords/test_assertions.py @@ -14,9 +14,7 @@ def under_test(): return create_test_object_for(AssertionKeywords) -def test_page_should_contain_string( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") mocker.patch("robot.api.logger.info") @@ -25,9 +23,7 @@ def test_page_should_contain_string( logger.info.assert_called_with('The string "abc" was found') -def test_page_should_contain_string_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="aBc") mocker.patch("robot.api.logger.info") @@ -36,249 +32,179 @@ def test_page_should_contain_string_ignore_case( logger.info.assert_called_with('The string "abc" was found') -def test_page_should_contain_string_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='The string "def" was not found'): under_test.page_should_contain_string("def") -def test_page_should_contain_string_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): under_test.page_should_contain_string("def", error_message="my error message") -def test_page_should_not_contain_string( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_string(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_not_contain_string("ABC") -def test_page_should_not_contain_string_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_string_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_not_contain_string("def", ignore_case=True) -def test_page_should_not_contain_string_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_string_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='The string "ABC" was found'): under_test.page_should_not_contain_string("ABC", ignore_case=True) -def test_page_should_not_contain_string_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_string_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): - under_test.page_should_not_contain_string( - "abc", error_message="my error message" - ) + under_test.page_should_not_contain_string("abc", error_message="my error message") -def test_page_should_contain_any_string( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_any_string(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_contain_any_string(["abc", "def"]) -def test_page_should_contain_any_string_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_any_string_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_contain_any_string(["ABC", "def"], ignore_case=True) -def test_page_should_contain_any_string_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_any_string_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") - with pytest.raises( - Exception, match=re.escape("The strings \"['def', 'ghi']\" were not found") - ): + with pytest.raises(Exception, match=re.escape("The strings \"['def', 'ghi']\" were not found")): under_test.page_should_contain_any_string(["def", "ghi"]) -def test_page_should_contain_any_string_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_any_string_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): - under_test.page_should_contain_any_string( - ["def", "ghi"], error_message="my error message" - ) + under_test.page_should_contain_any_string(["def", "ghi"], error_message="my error message") -def test_page_should_contain_all_strings( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_all_strings(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", side_effect=["abc", "def"]) under_test.page_should_contain_all_strings(["abc", "def"]) -def test_page_should_contain_all_strings_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_all_strings_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", side_effect=["AbC", "DeF"]) under_test.page_should_contain_all_strings(["abc", "def"], ignore_case=True) -def test_page_should_contain_all_strings_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_all_strings_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value=["def"]) with pytest.raises(Exception, match='The string "ghi" was not found'): under_test.page_should_contain_all_strings(["def", "ghi"]) -def test_page_should_contain_all_strings_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_all_strings_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): - under_test.page_should_contain_all_strings( - ["abc", "def"], error_message="my error message" - ) + under_test.page_should_contain_all_strings(["abc", "def"], error_message="my error message") -def test_page_should_not_contain_any_string( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_any_string(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_not_contain_any_string(["def", "ghi"]) -def test_page_should_not_contain_any_string_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_any_string_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='The string "abc" was found'): under_test.page_should_not_contain_any_string(["abc", "def"]) -def test_page_should_not_contain_any_string_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_any_string_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="ABC") with pytest.raises(Exception, match='The string "abc" was found'): under_test.page_should_not_contain_any_string(["abc", "def"], ignore_case=True) -def test_page_should_not_contain_any_string_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_any_string_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): - under_test.page_should_not_contain_any_string( - ["abc", "def"], error_message="my error message" - ) + under_test.page_should_not_contain_any_string(["abc", "def"], error_message="my error message") -def test_page_should_not_contain_all_strings( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_all_strings(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_not_contain_all_strings(["def", "ghi"]) -def test_page_should_not_contain_all_strings_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_all_strings_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='The string "abc" was found'): under_test.page_should_not_contain_all_strings(["ABC", "def"], ignore_case=True) -def test_page_should_not_contain_all_strings_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_all_strings_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='The string "abc" was found'): under_test.page_should_not_contain_all_strings(["abc", "def"]) -def test_page_should_not_contain_all_strings_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_all_strings_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): - under_test.page_should_not_contain_all_strings( - ["abc", "def"], error_message="my error message" - ) + under_test.page_should_not_contain_all_strings(["abc", "def"], error_message="my error message") -def test_page_should_contain_string_x_times( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_x_times(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="a") under_test.page_should_contain_string_x_times("a", 24) -def test_page_should_contain_string_x_times_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_x_times_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="a") under_test.page_should_contain_string_x_times("A", 24, ignore_case=True) -def test_page_should_contain_string_x_times_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_x_times_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="a") - with pytest.raises( - Exception, match='The string "a" was not found "1" times, it appears "24" times' - ): + with pytest.raises(Exception, match='The string "a" was not found "1" times, it appears "24" times'): under_test.page_should_contain_string_x_times("a", 1) - with pytest.raises( - Exception, match='The string "b" was not found "1" times, it appears "0" times' - ): + with pytest.raises(Exception, match='The string "b" was not found "1" times, it appears "0" times'): under_test.page_should_contain_string_x_times("b", 1) -def test_page_should_contain_string_x_times_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_string_x_times_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="a") with pytest.raises(Exception, match="my error message"): - under_test.page_should_contain_string_x_times( - "b", 1, error_message="my error message" - ) + under_test.page_should_contain_string_x_times("b", 1, error_message="my error message") def test_page_should_match_regex(mocker: MockerFixture, under_test: AssertionKeywords): @@ -287,108 +213,74 @@ def test_page_should_match_regex(mocker: MockerFixture, under_test: AssertionKey under_test.page_should_match_regex(r"\w+") -def test_page_should_match_regex_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_match_regex_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") - with pytest.raises( - Exception, match=re.escape(r'No matches found for "\d+" pattern') - ): + with pytest.raises(Exception, match=re.escape(r'No matches found for "\d+" pattern')): under_test.page_should_match_regex(r"\d+") -def test_page_should_not_match_regex( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_match_regex(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_not_match_regex(r"\d+") -def test_page_should_not_match_regex_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_match_regex_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="a") - with pytest.raises( - Exception, match=re.escape('There are matches found for "[a]+" pattern') - ): + with pytest.raises(Exception, match=re.escape('There are matches found for "[a]+" pattern')): under_test.page_should_not_match_regex("[a]+") -def test_page_should_contain_match( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_match(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_contain_match("*a?c*") -def test_page_should_contain_match_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_match_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") - with pytest.raises( - Exception, match=re.escape('No matches found for "*e?g*" pattern') - ): + with pytest.raises(Exception, match=re.escape('No matches found for "*e?g*" pattern')): under_test.page_should_contain_match("*e?g*") -def test_page_should_contain_match_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_match_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="ABC") under_test.page_should_contain_match("*a?c*", ignore_case=True) -def test_page_should_contain_match_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_contain_match_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): under_test.page_should_contain_match("*def*", error_message="my error message") -def test_page_should_not_contain_match( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_match(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") under_test.page_should_not_contain_match("*def*") -def test_page_should_not_contain_match_fails( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_match_fails(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") - with pytest.raises( - Exception, match=re.escape('There are matches found for "*abc*" pattern') - ): + with pytest.raises(Exception, match=re.escape('There are matches found for "*abc*" pattern')): under_test.page_should_not_contain_match("*abc*") -def test_page_should_not_contain_match_ignore_case( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_match_ignore_case(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") - with pytest.raises( - Exception, match=re.escape('There are matches found for "*abc*" pattern') - ): + with pytest.raises(Exception, match=re.escape('There are matches found for "*abc*" pattern')): under_test.page_should_not_contain_match("*ABC*", ignore_case=True) -def test_page_should_not_contain_match_custom_message( - mocker: MockerFixture, under_test: AssertionKeywords -): +def test_page_should_not_contain_match_custom_message(mocker: MockerFixture, under_test: AssertionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match="my error message"): - under_test.page_should_not_contain_match( - "*abc*", error_message="my error message" - ) + under_test.page_should_not_contain_match("*abc*", error_message="my error message") diff --git a/utest/Mainframe3270/keywords/test_connection.py b/utest/Mainframe3270/keywords/test_connection.py index e280ff4..ba539f8 100644 --- a/utest/Mainframe3270/keywords/test_connection.py +++ b/utest/Mainframe3270/keywords/test_connection.py @@ -1,5 +1,4 @@ import os -import re from unittest.mock import mock_open, patch import pytest @@ -31,9 +30,7 @@ def test_open_connection(mocker: MockerFixture, under_test: ConnectionKeywords): assert ConnectionCache.register.call_args[0][1] is None -def test_open_connection_with_alias( - mocker: MockerFixture, under_test: ConnectionKeywords -): +def test_open_connection_with_alias(mocker: MockerFixture, under_test: ConnectionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.connect") mocker.patch("robot.utils.ConnectionCache.register") @@ -44,9 +41,7 @@ def test_open_connection_with_alias( assert ConnectionCache.register.call_args[0][1] == "myalias" -def test_open_connection_returns_index( - mocker: MockerFixture, under_test: ConnectionKeywords -): +def test_open_connection_returns_index(mocker: MockerFixture, under_test: ConnectionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.connect") mocker.patch("robot.utils.ConnectionCache.register", return_value=1) @@ -63,9 +58,7 @@ def test_open_connection_with_lu(mocker: MockerFixture, under_test: ConnectionKe Emulator.connect.assert_called_with("lu@myhost:23") -def test_open_connection_with_port( - mocker: MockerFixture, under_test: ConnectionKeywords -): +def test_open_connection_with_port(mocker: MockerFixture, under_test: ConnectionKeywords): mocker.patch("Mainframe3270.py3270.Emulator.connect") under_test.open_connection("myhost", port=2222) @@ -73,16 +66,14 @@ def test_open_connection_with_port( Emulator.connect.assert_called_with("myhost:2222") -def test_open_connection_with_extra_args( - mocker: MockerFixture, under_test: ConnectionKeywords -): +def test_open_connection_with_extra_args(mocker: MockerFixture, under_test: ConnectionKeywords): extra_args = ["-xrm", "*blankFill: true"] mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) mocker.patch("Mainframe3270.py3270.Emulator.connect") under_test.open_connection("myhost", extra_args=extra_args) - Emulator.__init__.assert_called_with(True, 30.0, extra_args) + Emulator.__init__.assert_called_with(True, 30.0, extra_args, "2") def test_open_connection_with_port_from_argument_and_from_extra_args( @@ -101,6 +92,26 @@ def test_open_connection_with_port_from_argument_and_from_extra_args( ) +def test_open_connection_with_default_model(mocker: MockerFixture, under_test: ConnectionKeywords): + mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + + under_test.open_connection("myhost") + + Emulator.__init__.assert_called_with(True, 30.0, [], "2") + + +def test_open_connection_with_model_from_extra_args(mocker: MockerFixture, under_test: ConnectionKeywords): + mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + model = "4" + extra_args = ["-xrm", f"*model: {model}"] + + under_test.open_connection("myhost", extra_args=extra_args) + + Emulator.__init__.assert_called_with(True, 30.0, extra_args, model) + + def test_process_args_returns_empty_list(under_test: ConnectionKeywords): args = None @@ -151,6 +162,55 @@ def test_process_args_from_multiline_file_with_comments(under_test: ConnectionKe assert processed_args == args_from_file +@pytest.mark.parametrize( + ("model_arg", "expected_model"), + [ + (["-xrm", "wc3270.model: 2"], "2"), + (["-xrm", "ws3270.model: 2"], "2"), + (["-xrm", "x3270.model: 2"], "2"), + (["-xrm", "s3270.model: 2"], "2"), + (["-xrm", "*model: 2"], "2"), + (["-xrm", "*model:2"], "2"), + (["-xrm", "*model:3"], "3"), + (["-xrm", "*model:4"], "4"), + (["-xrm", "*model:5"], "5"), + (["-xrm", "*model:3278-2"], "3278-2"), + (["-xrm", "*model:3278-2-E"], "3278-2-E"), + (["-xrm", "*model:3279-2"], "3279-2"), + (["-xrm", "*model:3279-2-E"], "3279-2-E"), + (["-xrm", "*model:3278-3"], "3278-3"), + (["-xrm", "*model:3278-3-E"], "3278-3-E"), + (["-xrm", "*model:3279-3"], "3279-3"), + (["-xrm", "*model:3279-3-E"], "3279-3-E"), + (["-xrm", "*model:3278-4"], "3278-4"), + (["-xrm", "*model:3278-4-E"], "3278-4-E"), + (["-xrm", "*model:3279-4"], "3279-4"), + (["-xrm", "*model:3279-4-E"], "3279-4-E"), + (["-xrm", "*model:3278-5"], "3278-5"), + (["-xrm", "*model:3278-5-E"], "3278-5-E"), + (["-xrm", "*model:3279-5"], "3279-5"), + (["-xrm", "*model:3279-5-E"], "3279-5-E"), + ], +) +def test_get_model_from_list_or_file_with_list(under_test: ConnectionKeywords, model_arg: list, expected_model: str): + model = under_test._get_model_from_list_or_file(model_arg) + + assert model == expected_model + + +def test_get_model_from_list_or_file_with_file(mocker: MockerFixture, under_test: ConnectionKeywords): + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") + with patch("builtins.open", mock_open(read_data="*hostname: pub400.com\n*model: 5")): + model = under_test._get_model_from_list_or_file("session.x3270") + + assert model == "5" + + +def test_get_model_from_list_or_file_returns_None(under_test: ConnectionKeywords): + assert under_test._get_model_from_list_or_file([]) is None + assert under_test._get_model_from_list_or_file(None) is None + + @pytest.mark.parametrize( ("args", "expected"), [ @@ -199,66 +259,91 @@ def test_check_session_file_extension( def test_contains_hostname_raises_ValueError(under_test: ConnectionKeywords): - with patch( - "builtins.open", mock_open(read_data="wc3270.port: 992\n") - ) as session_file: + with patch("builtins.open", mock_open(read_data="wc3270.port: 992\n")): with pytest.raises( ValueError, match="Your session file needs to specify the hostname resource to set up the connection", ): - under_test._check_contains_hostname(session_file) + under_test._check_contains_hostname("wc3270.session") @pytest.mark.parametrize( - "model", + ("os_name", "visible"), [ - "wc3270.model: 2", - "ws3270.model:2", - "x3270.model: 2", - "s3270.model: 2", - "*model:2", - "", + ("nt", False), + ("posix", True), + ("posix", False), ], ) -def test_check_model(model: str, under_test: ConnectionKeywords): - with patch("builtins.open", mock_open(read_data=model)) as session_file: - under_test._check_model(session_file) +def test_open_connection_from_session_file_uses_default_model( + under_test: ConnectionKeywords, os_name: str, visible: bool, mocker: MockerFixture +): + mocker.patch("Mainframe3270.keywords.connection.os_name", os_name) + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") + mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) + under_test.visible = visible + + with patch("builtins.open", mock_open(read_data="*hostname: pub400.com")): + under_test.open_connection_from_session_file("session.s3270") + + Emulator.__init__.assert_called_with(visible, 30.0, ["session.s3270"], "2") + + +def test_open_connection_from_session_file_uses_default_model_for_wc3270( + mocker: MockerFixture, under_test: ConnectionKeywords +): + mocker.patch("Mainframe3270.keywords.connection.os_name", "nt") + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") + mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + under_test.visible = True + with patch("builtins.open", mock_open(read_data="*hostname: pub400.com")): + under_test.open_connection_from_session_file("session.s3270") + + Emulator.__init__.assert_called_with(True, 30.0, model="2") @pytest.mark.parametrize( - ("model_string", "model"), + ("os_name", "visible"), [ - ("wc3270.model: 4", "4"), - ("ws3270.model:4", "4"), - ("x3270.model: 5", "5"), - ("s3270.model: 3279-4-E", "3279-4-E"), - ("*model: 3278-4", "3278-4"), + ("nt", False), + ("posix", True), + ("posix", False), ], ) -def test_check_model_raises_ValueError( - model_string: str, model: SystemError, under_test: ConnectionKeywords +def test_open_connection_from_session_file_uses_model_from_file( + mocker: MockerFixture, os_name: str, visible: bool, under_test: ConnectionKeywords ): - with patch("builtins.open", mock_open(read_data=model_string)) as session_file: - with pytest.raises( - ValueError, - match=re.escape( - f'Robot-Framework-Mainframe-3270-Library currently only supports model "2", ' - f'the model you specified in your session file was "{model}". ' - f'Please change it to "2", using either the session wizard if you are on Windows, ' - f'or by editing the model resource like this "*model: 2"' - ), - ): - under_test._check_model(session_file) + mocker.patch("Mainframe3270.keywords.connection.os_name", os_name) + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") + mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) + under_test.visible = visible + + with patch("builtins.open", mock_open(read_data="*hostname: pub400.com\n*model: 5")): + under_test.open_connection_from_session_file("session.x3270") + Emulator.__init__.assert_called_with(visible, 30.0, ["session.x3270"], "5") -def test_open_connection_from_session_file_registers_connection( + +def test_open_connection_from_session_file_uses_model_from_file_for_wc3270( mocker: MockerFixture, under_test: ConnectionKeywords ): - mocker.patch( - "Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension" - ) + mocker.patch("Mainframe3270.keywords.connection.os_name", "nt") + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") + mocker.patch("Mainframe3270.py3270.Emulator.__init__", return_value=None) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + under_test.visible = True + with patch("builtins.open", mock_open(read_data="*hostname: pub400.com\nwc3270.model: 5")): + under_test.open_connection_from_session_file("session.wc3270") + + Emulator.__init__.assert_called_with(True, 30.0, model="5") + + +def test_open_connection_from_session_file_registers_connection(mocker: MockerFixture, under_test: ConnectionKeywords): + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_contains_hostname") - mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_model") + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._get_model_from_list_or_file", return_value="2") + mocker.patch("Mainframe3270.py3270.Emulator.connect") mocker.patch("robot.utils.ConnectionCache.register") under_test.open_connection_from_session_file("session.wc3270") @@ -270,11 +355,10 @@ def test_open_connection_from_session_file_registers_connection( def test_open_connection_from_session_file_registers_connection_with_alias( mocker: MockerFixture, under_test: ConnectionKeywords ): - mocker.patch( - "Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension" - ) + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_contains_hostname") - mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_model") + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._get_model_from_list_or_file", return_value="3") + mocker.patch("Mainframe3270.py3270.Emulator.connect") mocker.patch("robot.utils.ConnectionCache.register") under_test.open_connection_from_session_file("session.wc3270", "myalias") @@ -283,14 +367,11 @@ def test_open_connection_from_session_file_registers_connection_with_alias( assert ConnectionCache.register.call_args[0][1] == "myalias" -def test_open_connection_from_session_file_returns_index( - mocker: MockerFixture, under_test: ConnectionKeywords -): - mocker.patch( - "Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension" - ) +def test_open_connection_from_session_file_returns_index(mocker: MockerFixture, under_test: ConnectionKeywords): + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_session_file_extension") mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_contains_hostname") - mocker.patch("Mainframe3270.keywords.ConnectionKeywords._check_model") + mocker.patch("Mainframe3270.keywords.ConnectionKeywords._get_model_from_list_or_file", return_value="4") + mocker.patch("Mainframe3270.py3270.Emulator.connect") mocker.patch("robot.utils.ConnectionCache.register", return_value=1) index = under_test.open_connection_from_session_file("session.wc3270") diff --git a/utest/Mainframe3270/keywords/test_read_write.py b/utest/Mainframe3270/keywords/test_read_write.py index 09a0f43..50b6f6a 100644 --- a/utest/Mainframe3270/keywords/test_read_write.py +++ b/utest/Mainframe3270/keywords/test_read_write.py @@ -22,9 +22,7 @@ def test_read(under_test: ReadWriteKeywords, mocker: MockerFixture): def test_read_all_screen(under_test: ReadWriteKeywords, mocker: MockerFixture): - mocker.patch( - "Mainframe3270.py3270.Emulator.read_all_screen", return_value="all screen" - ) + mocker.patch("Mainframe3270.py3270.Emulator.read_all_screen", return_value="all screen") content = under_test.read_all_screen() diff --git a/utest/Mainframe3270/keywords/test_screenshot.py b/utest/Mainframe3270/keywords/test_screenshot.py index bf10830..10f8fc2 100644 --- a/utest/Mainframe3270/keywords/test_screenshot.py +++ b/utest/Mainframe3270/keywords/test_screenshot.py @@ -22,9 +22,7 @@ def test_set_screenshot_folder(under_test: ScreenshotKeywords): assert under_test.img_folder == os.getcwd() -def test_set_screenshot_folder_nonexistent( - mocker: MockerFixture, under_test: ScreenshotKeywords -): +def test_set_screenshot_folder_nonexistent(mocker: MockerFixture, under_test: ScreenshotKeywords): mocker.patch("robot.api.logger.error") mocker.patch("robot.api.logger.warn") path = os.path.join(os.getcwd(), "nonexistent") @@ -32,9 +30,7 @@ def test_set_screenshot_folder_nonexistent( under_test.set_screenshot_folder(path) logger.error.assert_called_with('Given screenshots path "%s" does not exist' % path) - logger.warn.assert_called_with( - 'Screenshots will be saved in "%s"' % under_test.img_folder - ) + logger.warn.assert_called_with('Screenshots will be saved in "%s"' % under_test.img_folder) def test_take_screenshot(mocker: MockerFixture, under_test: ScreenshotKeywords): @@ -55,9 +51,7 @@ def test_take_screenshot(mocker: MockerFixture, under_test: ScreenshotKeywords): assert filepath == "./screenshot_1000.html" -def test_take_screenshot_with_filename_prefix( - mocker: MockerFixture, under_test: ScreenshotKeywords -): +def test_take_screenshot_with_filename_prefix(mocker: MockerFixture, under_test: ScreenshotKeywords): mocker.patch("Mainframe3270.py3270.Emulator.save_screen") mocker.patch("robot.api.logger.write") mocker.patch("time.time", return_value=1.0) diff --git a/utest/Mainframe3270/keywords/test_wait_and_timeout.py b/utest/Mainframe3270/keywords/test_wait_and_timeout.py index 5856845..fa5bb26 100644 --- a/utest/Mainframe3270/keywords/test_wait_and_timeout.py +++ b/utest/Mainframe3270/keywords/test_wait_and_timeout.py @@ -86,27 +86,21 @@ def test_wait_until_string(mocker: MockerFixture, under_test: WaitAndTimeoutKeyw assert txt == "abc" -def test_wait_until_string_string_not_found( - mocker: MockerFixture, under_test: WaitAndTimeoutKeywords -): +def test_wait_until_string_string_not_found(mocker: MockerFixture, under_test: WaitAndTimeoutKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='String "def" not found in 1 second'): under_test.wait_until_string("def", 1) -def test_wait_until_string_with_time_time_string( - mocker: MockerFixture, under_test: WaitAndTimeoutKeywords -): +def test_wait_until_string_with_time_time_string(mocker: MockerFixture, under_test: WaitAndTimeoutKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='String "def" not found in 500 milliseconds'): under_test.wait_until_string("def", "500 millis") -def test_wait_until_string_with_time_timer_string( - mocker: MockerFixture, under_test: WaitAndTimeoutKeywords -): +def test_wait_until_string_with_time_timer_string(mocker: MockerFixture, under_test: WaitAndTimeoutKeywords): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="abc") with pytest.raises(Exception, match='String "def" not found in 500 milliseconds'): diff --git a/utest/Mainframe3270/test__init__.py b/utest/Mainframe3270/test__init__.py index 752166c..d0869f9 100644 --- a/utest/Mainframe3270/test__init__.py +++ b/utest/Mainframe3270/test__init__.py @@ -12,6 +12,7 @@ def test_default_args(): assert under_test.wait_time == 0.5 assert under_test.wait_time_after_write == 0.0 assert under_test.img_folder == "." + assert under_test.model == "2" under_test.mf is None diff --git a/utest/Mainframe3270/test_librarycomponent.py b/utest/Mainframe3270/test_librarycomponent.py index 06f172a..82548f7 100644 --- a/utest/Mainframe3270/test_librarycomponent.py +++ b/utest/Mainframe3270/test_librarycomponent.py @@ -21,6 +21,7 @@ def test_librarycomponent_returns_common_attributes(): assert library.cache == under_test.cache assert library.mf == under_test.mf assert library.output_folder == under_test.output_folder + assert library.model == under_test.model def test_can_set_visible(): diff --git a/utest/Mainframe3270/test_run_on_failure.py b/utest/Mainframe3270/test_run_on_failure.py index e4b9c3d..4b6599c 100644 --- a/utest/Mainframe3270/test_run_on_failure.py +++ b/utest/Mainframe3270/test_run_on_failure.py @@ -36,6 +36,4 @@ def test_run_on_failure_could_not_be_run(mocker: MockerFixture): with pytest.raises(Exception, match="my error message"): under_test.run_keyword("Keyword", None, None) - logger.warn.assert_called_with( - "Keyword 'Keyword' could not be run on failure: my error message" - ) + logger.warn.assert_called_with("Keyword 'Keyword' could not be run on failure: my error message") diff --git a/utest/py3270/test_command.py b/utest/py3270/test_command.py index 1910700..0b89957 100644 --- a/utest/py3270/test_command.py +++ b/utest/py3270/test_command.py @@ -4,8 +4,7 @@ from pytest_mock import MockerFixture # fmt: off -from Mainframe3270.py3270 import (Command, CommandError, Emulator, s3270App, - wc3270App, ws3270App, x3270App) +from Mainframe3270.py3270 import Command, CommandError, Emulator, s3270App, wc3270App, ws3270App, x3270App # fmt: on @@ -93,7 +92,5 @@ def test_handle_result_not_ok_or_error(mocker: MockerFixture): app = wc3270App() under_test = Command(app, b"abc") - with pytest.raises( - ValueError, match='expected "ok" or "error" result, but received: abc' - ): + with pytest.raises(ValueError, match='expected "ok" or "error" result, but received: abc'): under_test.execute() diff --git a/utest/py3270/test_emulator.py b/utest/py3270/test_emulator.py index 1851cfc..d23f2d0 100644 --- a/utest/py3270/test_emulator.py +++ b/utest/py3270/test_emulator.py @@ -3,6 +3,7 @@ import pytest from pytest_mock import MockerFixture +from Mainframe3270 import py3270 from Mainframe3270.py3270 import Emulator, TerminatedError @@ -11,7 +12,7 @@ def test_emulator_default_args(): under_test = Emulator() assert under_test.app.executable == "ws3270" - assert under_test.app.args == ["-xrm", "ws3270.unlockDelay: False"] + assert under_test.app.args == ["-xrm", "ws3270.unlockDelay: False", "-xrm", "*model: 2"] @pytest.mark.usefixtures("mock_windows") @@ -23,7 +24,7 @@ def test_emulator_visible(): "-xrm", "wc3270.unlockDelay: False", "-xrm", - "wc3270.model: 2", + "*model: 2", ] @@ -50,23 +51,78 @@ def test_emulator_with_extra_args(): assert under_test.app.args > extra_args +@pytest.mark.usefixtures("mock_windows") +def test_emulator_with_model_default_model(): + under_test = Emulator() + + assert under_test.model == "2", 'default model should be "2"' + + +@pytest.mark.usefixtures("mock_windows") +def test_emulator_with_model(): + under_test = Emulator(model="4") + + assert under_test.model == "4" + + +@pytest.mark.usefixtures("mock_windows") +@pytest.mark.parametrize( + ("os_name", "visible", "model"), + [ + ("nt", True, "2"), + ("nt", False, "2"), + ("nt", True, "3"), + ("nt", False, "3"), + ("posix", True, "2"), + ("posix", False, "2"), + ("posix", True, "3"), + ("posix", False, "3"), + ], +) +def test_emulator_ws3270App_has_model_as_last_arg(visible: bool, os_name: str, model: str): + py3270.os_name = os_name + under_test = Emulator(visible, model=model) + + assert under_test.app.args[-2:] == ["-xrm", f"*model: {model}"] + + +@pytest.mark.usefixtures("mock_windows") +@pytest.mark.parametrize( + ("model", "model_dimensions"), + [ + ("2", {"rows": 24, "columns": 80}), + ("3", {"rows": 32, "columns": 80}), + ("4", {"rows": 43, "columns": 80}), + ("5", {"rows": 27, "columns": 132}), + ], +) +def test_emulator_sets_model_dimensions(model, model_dimensions): + under_test = Emulator(model=model) + + assert under_test.model_dimensions == model_dimensions + + +@pytest.mark.usefixtures("mock_windows") +def test_set_model_dimensions_raises_ValueError(): + under_test = Emulator() + + with pytest.raises(ValueError, match=r"Model should be one of .+, but was 'wrong model'"): + under_test._set_model_dimensions("wrong model") + + @pytest.mark.usefixtures("mock_windows") def test_exec_command_when_is_terminated(): under_test = Emulator() under_test.is_terminated = True - with pytest.raises( - TerminatedError, match="This Emulator instance has been terminated" - ): + with pytest.raises(TerminatedError, match="This Emulator instance has been terminated"): under_test.exec_command(b"abc") @pytest.mark.usefixtures("mock_windows") def test_terminate_BrokenPipeError(mocker: MockerFixture): mocker.patch("Mainframe3270.py3270.wc3270App.close") - mocker.patch( - "Mainframe3270.py3270.Emulator.exec_command", side_effect=BrokenPipeError - ) + mocker.patch("Mainframe3270.py3270.Emulator.exec_command", side_effect=BrokenPipeError) under_test = Emulator() under_test.terminate() @@ -79,9 +135,7 @@ def test_terminate_socket_error(mocker: MockerFixture): mock_os_error = OSError() mock_os_error.errno = errno.ECONNRESET mocker.patch("Mainframe3270.py3270.wc3270App.close") - mocker.patch( - "Mainframe3270.py3270.Emulator.exec_command", side_effect=mock_os_error - ) + mocker.patch("Mainframe3270.py3270.Emulator.exec_command", side_effect=mock_os_error) under_test = Emulator() under_test.terminate() @@ -176,13 +230,21 @@ def test_string_get_calls__check_limits(mocker: MockerFixture): @pytest.mark.usefixtures("mock_windows") -def test_string_get_exceeds_x_axis(mocker: MockerFixture): - under_test = Emulator(True) +@pytest.mark.parametrize( + ("model", "length"), + [ + ("2", 72), + ("3", 72), + ("4", 72), + ("5", 124), + ], +) +def test_string_get_exceeds_x_axis(mocker: MockerFixture, model: str, length: int): + mocker.patch("Mainframe3270.py3270.wc3270App.readline") + under_test = Emulator(True, model=model) - with pytest.raises( - Exception, match="You have exceeded the x-axis limit of the mainframe screen" - ): - under_test.string_get(1, 10, 72) + with pytest.raises(Exception, match="You have exceeded the x-axis limit of the mainframe screen"): + under_test.string_get(1, 10, length) @pytest.mark.usefixtures("mock_windows") @@ -209,6 +271,18 @@ def test_search_string_ignoring_case(mocker: MockerFixture): assert under_test.search_string("abc", True) +@pytest.mark.usefixtures("mock_windows") +@pytest.mark.parametrize(("model", "rows", "columns"), [("2", 24, 80), ("3", 32, 80), ("4", 43, 80), ("5", 27, 132)]) +def test_search_string_with_different_model_dimensions(mocker: MockerFixture, model: str, rows: int, columns: int): + mocker.patch("Mainframe3270.py3270.Emulator.string_get") + under_test = Emulator(model=model) + + under_test.search_string("abc") + + assert Emulator.string_get.call_count == rows + Emulator.string_get.assert_called_with(rows, 1, columns) + + @pytest.mark.usefixtures("mock_windows") def test_read_all_screen(mocker: MockerFixture): mocker.patch("Mainframe3270.py3270.Emulator.string_get", return_value="a") @@ -219,6 +293,18 @@ def test_read_all_screen(mocker: MockerFixture): assert content == "a" * 24 +@pytest.mark.usefixtures("mock_windows") +@pytest.mark.parametrize(("model", "rows", "columns"), [("2", 24, 80), ("3", 32, 80), ("4", 43, 80), ("5", 27, 132)]) +def test_read_all_screen_with_different_model_dimensions(mocker: MockerFixture, model: str, rows: int, columns: int): + mocker.patch("Mainframe3270.py3270.Emulator.string_get") + under_test = Emulator(model=model) + + under_test.read_all_screen() + + assert Emulator.string_get.call_count == rows + Emulator.string_get.assert_called_with(rows, 1, columns) + + @pytest.mark.usefixtures("mock_windows") def test_check_limits(): under_test = Emulator() @@ -228,14 +314,20 @@ def test_check_limits(): @pytest.mark.usefixtures("mock_windows") @pytest.mark.parametrize( - ("ypos", "xpos", "expected_error"), + ("model", "ypos", "xpos", "expected_error"), [ - (25, 80, "You have exceeded the y-axis limit of the mainframe screen"), - (24, 81, "You have exceeded the x-axis limit of the mainframe screen"), + ("2", 25, 80, "You have exceeded the y-axis limit of the mainframe screen"), + ("3", 33, 80, "You have exceeded the y-axis limit of the mainframe screen"), + ("4", 44, 80, "You have exceeded the y-axis limit of the mainframe screen"), + ("5", 28, 80, "You have exceeded the y-axis limit of the mainframe screen"), + ("2", 24, 81, "You have exceeded the x-axis limit of the mainframe screen"), + ("3", 32, 81, "You have exceeded the x-axis limit of the mainframe screen"), + ("4", 43, 81, "You have exceeded the x-axis limit of the mainframe screen"), + ("5", 27, 133, "You have exceeded the x-axis limit of the mainframe screen"), ], ) -def test_check_limits_raises_Exception(ypos, xpos, expected_error): - under_test = Emulator() +def test_check_limits_raises_Exception(model, ypos, xpos, expected_error): + under_test = Emulator(model=model) with pytest.raises(Exception, match=expected_error): under_test._check_limits(ypos, xpos)