diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 59de5b7..d3262af 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -5,21 +5,25 @@ on: [push, pull_request] jobs: tests: + env: + ROBOT_OPTIONS: --exclude no-ci --loglevel DEBUG --outputdir logs runs-on: ${{ matrix.os }} strategy: + # pub400 closes the connection if there are too many requests coming from the same IP address + max-parallel: 1 matrix: - python-version: [3.7, 3.10.0] + python-version: ["3.7", "3.10"] os: [ubuntu-latest, windows-latest] fail-fast: false steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Set up python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} @@ -50,7 +54,7 @@ jobs: coverage xml - name: Upload unit test coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: files: coverage.xml flags: unit @@ -59,28 +63,25 @@ jobs: - name: Run atests with coverage linux if: matrix.os == 'ubuntu-latest' run: | - LANG=en_US.iso88591 xvfb-run coverage run --branch --source Mainframe3270/ -m robot --exclude no-ci --loglevel DEBUG atest/ + LANG=en_US.iso88591 xvfb-run coverage run --branch --source Mainframe3270/ -m robot $ROBOT_OPTIONS atest/ coverage report coverage xml - name: Run atests with coverage windows if: matrix.os == 'windows-latest' run: | - coverage run --branch --source Mainframe3270/ -m robot --exclude no-ci --loglevel DEBUG atest/ + coverage run --branch --source Mainframe3270/ -m robot $ROBOT_OPTIONS atest/ coverage report coverage xml - name: Upload acceptance tests coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: files: coverage.xml flags: acceptance - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 if: ${{ always() }} with: name: Tests results python${{ matrix.python-version }} - ${{ matrix.os }} - path: | - log.html - report.html - output.xml + path: logs/ diff --git a/Mainframe3270/py3270.py b/Mainframe3270/py3270.py index 03ae64e..4e7a6e0 100644 --- a/Mainframe3270/py3270.py +++ b/Mainframe3270/py3270.py @@ -4,6 +4,7 @@ import subprocess import time import warnings +from abc import ABC, abstractmethod from os import name as os_name log = logging.getLogger(__name__) @@ -115,11 +116,20 @@ def __str__(self): return "STATUS: {0}".format(self.as_string) -class ExecutableAppLinux: - executable = None - args = ["-xrm", "s3270.unlockDelay: False"] +class ExecutableApp(ABC): + @property + @abstractmethod + def executable(self): + pass + + @property + @abstractmethod + def args(self): + pass - def __init__(self): + def __init__(self, extra_args=None): + if extra_args: + self.args = self.__class__.args + extra_args self.sp = None self.spawn_app() @@ -147,12 +157,35 @@ def readline(self): return self.sp.stdout.readline() -class ExecutableAppWin: - executable = None - args = ["-xrm", "wc3270.unlockDelay: False"] +class x3270App(ExecutableApp): + executable = "x3270" + # Per Paul Mattes, in the first days of x3270, there were servers that + # would unlock the keyboard before they had processed the command. To + # 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"] + + +class s3270App(ExecutableApp): + executable = "s3270" + # see notes for args in x3270App + args = ["-xrm", "s3270.unlockDelay: False"] + + +class NotConnectedException(Exception): + pass + + +class wc3270App(ExecutableApp): + executable = "wc3270" + # see notes for args in x3270App + args = ["-xrm", "wc3270.unlockDelay: False", "-xrm", "wc3270.model: 2"] script_port = 17938 - def __init__(self): + def __init__(self, extra_args=None): + if extra_args: + self.args = wc3270App.args + extra_args self.sp = None self.socket_fh = None @@ -204,61 +237,10 @@ def readline(self): return self.socket_fh.readline() -class x3270App(ExecutableAppLinux): - def __init__(self, extra_args): - self.executable = "x3270" - # Per Paul Mattes, in the first days of x3270, there were servers that - # would unlock the keyboard before they had processed the command. To - # 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. - self.args = [ - "-xrm", - "x3270.unlockDelay: False", - "-xrm", - "x3270.model: 2", - "-script", - ] - if extra_args: - self.args += extra_args - super().__init__() - - -class s3270App(ExecutableAppLinux): - def __init__(self, extra_args): - self.executable = "s3270" - # see notes for args in x3270App - self.args = ["-xrm", "s3270.unlockDelay: False"] - if extra_args: - self.args += extra_args - super().__init__() - - -class NotConnectedException(Exception): - pass - - -class wc3270App(ExecutableAppWin): - def __init__(self, extra_args): - super().__init__() - self.executable = "wc3270" - # see notes for args in x3270App - self.args = ["-xrm", "wc3270.unlockDelay: False", "-xrm", "wc3270.model: 2"] - if extra_args: - self.args += extra_args - - -class ws3270App(ExecutableAppWin): - def __init__(self, extra_args): - super().__init__() - self.executable = "ws3270" - # see notes for args in x3270App - self.args = [ - "-xrm", - "ws3270.unlockDelay: False", - ] - if extra_args: - self.args += extra_args +class ws3270App(ExecutableApp): + executable = "ws3270" + # see notes for args in x3270App + args = ["-xrm", "ws3270.unlockDelay: False"] class Emulator(object): @@ -267,15 +249,14 @@ class Emulator(object): with it. """ - def __init__(self, visible=False, timeout=30, extra_args=None, app=None, _sp=None): + def __init__(self, visible=False, timeout=30, extra_args=None, app=None): """ Create an emulator instance `visible` controls which executable will be used. `timeout` controls the timeout paramater to any Wait() command sent to x3270. - `_sp` is normally not used but can be set to a mock object - during testing. + `extra_args` allows sending parameters to the emulator executable """ self.app = app or self.create_app(visible, extra_args) self.is_terminated = False diff --git a/Mainframe3270/x3270.py b/Mainframe3270/x3270.py index a5153a8..c9e0547 100644 --- a/Mainframe3270/x3270.py +++ b/Mainframe3270/x3270.py @@ -57,14 +57,14 @@ def open_connection( port: int = 23, extra_args: Optional[Union[List[str], os.PathLike]] = None, ): - """Create a connection to a IBM3270 mainframe with the default port 23. - To make a connection with the mainframe you only need to specify the Hostname. - You can pass the Logical Unit Name and the Port as optional. + """Create a connection to an IBM3270 mainframe with the default port 23. + To establish a connection, only the hostname is required. Optional parameters include logical unit name (LU) and port. - If you wish, you can provide further configuration data via ``extra_args``. ``extra_args`` takes in a list, - or a path to a file, containing [https://x3270.miraheze.org/wiki/Category:Command-line_options|x3270 command line options]. + Additional configuration data can be provided through the `extra_args` parameter. + `extra_args` accepts either a list or a path to a file containing [x3270 command-line options](https://x3270.miraheze.org/wiki/Category:Command-line_options). Entries in the argfile can be on one line or multiple lines. Lines starting with "#" are considered comments. + | # example_argfile_oneline.txt | -accepthostname myhost.com @@ -74,8 +74,11 @@ def open_connection( | -charset french | -port 992 - Please make sure the arguments you are providing are available for your specific x3270 application and version. - For example the subset of [https://x3270.miraheze.org/wiki/Wc3270/Command-line_options|wc3270 line options]. + Please ensure that the arguments provided are available for your specific x3270 application and version. + Refer to the [wc3270 command-line options](https://x3270.miraheze.org/wiki/Wc3270/Command-line_options) for a subset of available options. + + Note: If you specify the port with the `-port` command-line option in `extra_args`, + it will take precedence over the `port` argument provided in the `Open Connection` keyword. Example: | Open Connection | Hostname | @@ -92,6 +95,13 @@ def open_connection( self.mf = Emulator(self.visible, self.timeout, extra_args) host_string = f"{LU}@{host}" if LU else host if self._port_in_extra_args(extra_args): + if port != 23: + logger.warn( + "The connection port has been specified both in the `port` argument and in `extra_args`. " + "The port specified in `extra_args` will take precedence over the `port` argument. " + "To avoid this warning, you can either remove the port command-line option from `extra_args`, " + "or leave the `port` argument at its default value of 23." + ) self.mf.connect(host_string) else: self.mf.connect(f"{host_string}:{port}") @@ -101,12 +111,11 @@ def _process_args(self, args) -> list: if not args: return [] elif isinstance(args, list): - for arg in args: - processed_args.append(arg) + processed_args = args elif isinstance(args, os.PathLike) or isinstance(args, str): with open(args) as file: for line in file: - if line.lstrip().startswith(r"#"): + if line.lstrip().startswith("#"): continue for arg in line.replace("\n", "").rstrip().split(" "): processed_args.append(arg) diff --git a/README.md b/README.md index ea55120..0a66007 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Mainframe3270 is a library for Robot Framework based on the [py3270 project](https://pypi.org/project/py3270/), a Python interface to x3270, an IBM 3270 terminal emulator. It provides an API to a x3270 or s3270 subprocess. ## Compatibility -Mainframe3270 requires Python 3. It is tested with Python 3.7 and 3.10.0, but should support all versions in between these. +Mainframe3270 requires Python 3. It is tested with Python 3.7 and 3.10, but should support all versions in between these. ## Installation diff --git a/utest/conftest.py b/utest/conftest.py new file mode 100644 index 0000000..7c2aa7d --- /dev/null +++ b/utest/conftest.py @@ -0,0 +1,14 @@ +import pytest +from pytest_mock import MockerFixture + + +@pytest.fixture +def mock_windows(mocker: MockerFixture): + mocker.patch("Mainframe3270.py3270.os_name", "nt") + mocker.patch("subprocess.Popen") + + +@pytest.fixture +def mock_posix(mocker: MockerFixture): + mocker.patch("Mainframe3270.py3270.os_name", "posix") + mocker.patch("subprocess.Popen") diff --git a/utest/py3270/test_command.py b/utest/py3270/test_command.py index 77c3ae0..1910700 100644 --- a/utest/py3270/test_command.py +++ b/utest/py3270/test_command.py @@ -3,12 +3,16 @@ import pytest from pytest_mock import MockerFixture -from Mainframe3270.py3270 import Command, CommandError, ExecutableAppLinux +# fmt: off +from Mainframe3270.py3270 import (Command, CommandError, Emulator, s3270App, + wc3270App, ws3270App, x3270App) + +# fmt: on def test_command_default(mocker: MockerFixture): mocker.patch("subprocess.Popen") - app = ExecutableAppLinux() + app = Emulator() under_test = Command(app, b"abc") @@ -21,7 +25,7 @@ def test_command_default(mocker: MockerFixture): def test_command_with_text_type(mocker: MockerFixture): mocker.patch("subprocess.Popen") mocker.patch("warnings.warn") - app = ExecutableAppLinux() + app = Emulator() under_test = Command(app, "abc") @@ -32,14 +36,14 @@ def test_command_with_text_type(mocker: MockerFixture): def test_execute(mocker: MockerFixture): mocker.patch("subprocess.Popen") mocker.patch( - "Mainframe3270.py3270.ExecutableAppLinux.readline", + "Mainframe3270.py3270.ExecutableApp.readline", side_effect=[ b"data: abc", b"U U U C(pub400.com) C 4 43 80 4 24 0x0 0.000", b"ok", ], ) - app = ExecutableAppLinux() + app = x3270App() under_test = Command(app, b"abc") under_test.execute() @@ -49,8 +53,8 @@ def test_execute(mocker: MockerFixture): def test_handle_result_quit(mocker: MockerFixture): mocker.patch("subprocess.Popen") - mocker.patch("Mainframe3270.py3270.ExecutableAppLinux.readline", return_value=b"") - app = ExecutableAppLinux() + mocker.patch("Mainframe3270.py3270.ExecutableApp.readline", return_value=b"") + app = x3270App() under_test = Command(app, b"Quit") under_test.execute() @@ -58,10 +62,8 @@ def test_handle_result_quit(mocker: MockerFixture): def test_handle_result_error(mocker: MockerFixture): mocker.patch("subprocess.Popen") - mocker.patch( - "Mainframe3270.py3270.ExecutableAppLinux.readline", return_value=b"error" - ) - app = ExecutableAppLinux() + mocker.patch("Mainframe3270.py3270.ExecutableApp.readline", return_value=b"error") + app = s3270App() under_test = Command(app, b"abc") with pytest.raises(CommandError, match="[no data message]"): @@ -71,14 +73,14 @@ def test_handle_result_error(mocker: MockerFixture): def test_handle_result_with_data(mocker: MockerFixture): mocker.patch("subprocess.Popen") mocker.patch( - "Mainframe3270.py3270.ExecutableAppLinux.readline", + "Mainframe3270.py3270.ExecutableApp.readline", side_effect=[ b"data: abc", b"U U U C(pub400.com) C 4 43 80 4 24 0x0 0.000", b"error", ], ) - app = ExecutableAppLinux() + app = ws3270App() under_test = Command(app, b"abc") with pytest.raises(CommandError, match="abc"): @@ -86,11 +88,9 @@ def test_handle_result_with_data(mocker: MockerFixture): def test_handle_result_not_ok_or_error(mocker: MockerFixture): - mocker.patch("subprocess.Popen") - mocker.patch( - "Mainframe3270.py3270.ExecutableAppLinux.readline", return_value=b"abc" - ) - app = ExecutableAppLinux() + mocker.patch("Mainframe3270.py3270.wc3270App.readline", return_value=b"abc") + mocker.patch("Mainframe3270.py3270.wc3270App.write") + app = wc3270App() under_test = Command(app, b"abc") with pytest.raises( diff --git a/utest/py3270/test_emulator.py b/utest/py3270/test_emulator.py index 6e0f0f7..5f1ad4a 100644 --- a/utest/py3270/test_emulator.py +++ b/utest/py3270/test_emulator.py @@ -1,32 +1,21 @@ import errno -import os import pytest +from pytest_mock import MockerFixture from Mainframe3270.py3270 import Emulator, TerminatedError -CURDIR = os.path.dirname(os.path.realpath(__file__)) - -@pytest.fixture -def mock_windows(mocker): - mocker.patch("Mainframe3270.py3270.os_name", "nt") - - -@pytest.fixture -def mock_posix(mocker): - mocker.patch("Mainframe3270.py3270.os_name", "posix") - mocker.patch("subprocess.Popen") - - -def test_emulator_default_args(mock_windows): +@pytest.mark.usefixtures("mock_windows") +def test_emulator_default_args(): under_test = Emulator() assert under_test.app.executable == "ws3270" assert under_test.app.args == ["-xrm", "ws3270.unlockDelay: False"] -def test_emulator_visible(mock_windows): +@pytest.mark.usefixtures("mock_windows") +def test_emulator_visible(): under_test = Emulator(visible=True) assert under_test.app.executable == "wc3270" @@ -38,19 +27,31 @@ def test_emulator_visible(mock_windows): ] -def test_emulator_none_windows(mock_posix): +@pytest.mark.usefixtures("mock_posix") +def test_emulator_none_windows(): under_test = Emulator() assert under_test.app.executable == "s3270" -def test_emulator_none_windows_visible(mock_posix): +@pytest.mark.usefixtures("mock_posix") +def test_emulator_none_windows_visible(): under_test = Emulator(visible=True) assert under_test.app.executable == "x3270" -def test_exec_command_when_is_terminated(mock_windows, mocker): +@pytest.mark.usefixtures("mock_windows") +def test_emulator_with_extra_args(): + extra_args = ["-cadir", "/path/to/ca_dir"] + under_test = Emulator(extra_args=extra_args) + + assert all(arg in under_test.app.args for arg in extra_args) + assert under_test.app.args > extra_args + + +@pytest.mark.usefixtures("mock_windows") +def test_exec_command_when_is_terminated(): under_test = Emulator() under_test.is_terminated = True @@ -60,8 +61,9 @@ def test_exec_command_when_is_terminated(mock_windows, mocker): under_test.exec_command(b"abc") -def test_terminate_BrokenPipeError(mock_windows, mocker): - mocker.patch("Mainframe3270.py3270.ExecutableAppWin.close") +@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 ) @@ -72,10 +74,11 @@ def test_terminate_BrokenPipeError(mock_windows, mocker): assert under_test.is_terminated -def test_terminate_socket_error(mock_windows, mocker): +@pytest.mark.usefixtures("mock_windows") +def test_terminate_socket_error(mocker: MockerFixture): mock_os_error = OSError() mock_os_error.errno = errno.ECONNRESET - mocker.patch("Mainframe3270.py3270.ExecutableAppWin.close") + mocker.patch("Mainframe3270.py3270.wc3270App.close") mocker.patch( "Mainframe3270.py3270.Emulator.exec_command", side_effect=mock_os_error ) @@ -86,8 +89,9 @@ def test_terminate_socket_error(mock_windows, mocker): under_test.is_terminated = True -def test_terminate_other_socket_error(mock_windows, mocker): - mocker.patch("Mainframe3270.py3270.ExecutableAppWin.close") +@pytest.mark.usefixtures("mock_windows") +def test_terminate_other_socket_error(mocker: MockerFixture): + mocker.patch("Mainframe3270.py3270.wc3270App.close") mocker.patch("Mainframe3270.py3270.Emulator.exec_command", side_effect=OSError) under_test = Emulator() @@ -95,37 +99,40 @@ def test_terminate_other_socket_error(mock_windows, mocker): under_test.terminate() -def test_is_connected(mock_windows, mocker): - mocker.patch("Mainframe3270.py3270.ExecutableAppWin.write") +@pytest.mark.usefixtures("mock_windows") +def test_is_connected(mocker: MockerFixture): + mocker.patch("Mainframe3270.py3270.wc3270App.write") mocker.patch( - "Mainframe3270.py3270.ExecutableAppWin.readline", + "Mainframe3270.py3270.wc3270App.readline", side_effect=[ b"data: abc", b"U U U C(pub400.com) C 4 43 80 4 24 0x0 0.000", b"ok", ], ) - under_test = Emulator() + under_test = Emulator(True) assert under_test.is_connected() -def test_is_not_connected(mock_windows, mocker): - mocker.patch("Mainframe3270.py3270.ExecutableAppWin.write") +@pytest.mark.usefixtures("mock_windows") +def test_is_not_connected(mocker: MockerFixture): + mocker.patch("Mainframe3270.py3270.wc3270App.write") mocker.patch( - "Mainframe3270.py3270.ExecutableAppWin.readline", + "Mainframe3270.py3270.wc3270App.readline", side_effect=[ b"data: abc", b"U U U N C 4 43 80 4 24 0x0 0.000", b"ok", ], ) - under_test = Emulator() + under_test = Emulator(True) assert not under_test.is_connected() -def test_is_connected_NotConnectedException(mock_windows): - under_test = Emulator() +@pytest.mark.usefixtures("mock_windows") +def test_is_connected_NotConnectedException(): + under_test = Emulator(True) assert not under_test.is_connected() diff --git a/utest/py3270/resources/argfile_multiline.txt b/utest/x3270/resources/argfile_multiline.txt similarity index 100% rename from utest/py3270/resources/argfile_multiline.txt rename to utest/x3270/resources/argfile_multiline.txt diff --git a/utest/py3270/resources/argfile_multiline_comments.txt b/utest/x3270/resources/argfile_multiline_comments.txt similarity index 100% rename from utest/py3270/resources/argfile_multiline_comments.txt rename to utest/x3270/resources/argfile_multiline_comments.txt diff --git a/utest/py3270/resources/argfile_oneline.txt b/utest/x3270/resources/argfile_oneline.txt similarity index 100% rename from utest/py3270/resources/argfile_oneline.txt rename to utest/x3270/resources/argfile_oneline.txt diff --git a/utest/x3270/test_connection.py b/utest/x3270/test_connection.py index 161ef1a..5214ea3 100644 --- a/utest/x3270/test_connection.py +++ b/utest/x3270/test_connection.py @@ -1,5 +1,7 @@ +import os import socket +import pytest from pytest_mock import MockerFixture import Mainframe3270 @@ -8,6 +10,8 @@ from .conftest import X3270_DEFAULT_ARGS +CURDIR = os.path.dirname(os.path.realpath(__file__)) + def test_open_connection(mocker: MockerFixture): m_connect = mocker.patch("Mainframe3270.py3270.Emulator.connect") @@ -17,7 +21,7 @@ def test_open_connection(mocker: MockerFixture): m_connect.assert_called_with("myhost:23") -def test_open_connection_existing_emulator(mocker): +def test_open_connection_existing_emulator(mocker: MockerFixture): mocker.patch("Mainframe3270.py3270.Emulator.create_app") mocker.patch("Mainframe3270.py3270.Emulator.connect") mocker.patch("Mainframe3270.x3270.close_connection") @@ -29,7 +33,7 @@ def test_open_connection_existing_emulator(mocker): Mainframe3270.x3270.close_connection.assert_called() -def test_open_connection_with_lu(mocker): +def test_open_connection_with_lu(mocker: MockerFixture): m_connect = mocker.patch("Mainframe3270.py3270.Emulator.connect") under_test = x3270(**X3270_DEFAULT_ARGS) under_test.open_connection("myhost", "lu") @@ -45,6 +49,83 @@ def test_open_connection_with_port(mocker: MockerFixture): m_connect.assert_called_with("myhost:2222") +def test_open_connection_with_port_from_argument_and_from_extra_args( + mocker: MockerFixture, +): + mocker.patch("Mainframe3270.py3270.Emulator.connect") + m_logger = mocker.patch("robot.api.logger.warn") + under_test = x3270(**X3270_DEFAULT_ARGS) + under_test.open_connection("myhost", port=12345, extra_args=["-port", "12345"]) + + m_logger.assert_called_with( + "The connection port has been specified both in the `port` argument and in `extra_args`. " + "The port specified in `extra_args` will take precedence over the `port` argument. " + "To avoid this warning, you can either remove the port command-line option from `extra_args`, " + "or leave the `port` argument at its default value of 23." + ) + + +def test_open_connection_with_extra_args_oneline(mocker: MockerFixture): + m_emulator = mocker.patch( + "Mainframe3270.py3270.Emulator.__init__", return_value=None + ) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + extra_args = os.path.join(CURDIR, "resources/argfile_oneline.txt") + + under_test = x3270(**X3270_DEFAULT_ARGS) + under_test.open_connection("myhost", extra_args=extra_args) + + args_from_file = ["-charset", "german"] + m_emulator.assert_called_with(True, 30.0, args_from_file) + + +@pytest.mark.usefixtures("mock_posix") +def test_open_connection_none_windows_extra_args_oneline( + mock_posix, mocker: MockerFixture +): + m_emulator = mocker.patch( + "Mainframe3270.py3270.Emulator.__init__", return_value=None + ) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + extra_args = os.path.join(CURDIR, "resources/argfile_oneline.txt") + + under_test = x3270(**X3270_DEFAULT_ARGS) + under_test.open_connection("myhost", extra_args=extra_args) + + args_from_file = ["-charset", "german"] + m_emulator.assert_called_with(True, 30.0, args_from_file) + + +@pytest.mark.usefixtures("mock_windows") +def test_open_connection_with_extra_args_multiline(mocker: MockerFixture): + m_emulator = mocker.patch( + "Mainframe3270.py3270.Emulator.__init__", return_value=None + ) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + extra_args = os.path.join(CURDIR, "resources/argfile_multiline.txt") + + under_test = x3270(**X3270_DEFAULT_ARGS) + under_test.open_connection("myhost", extra_args=extra_args) + + args_from_file = ["-charset", "bracket", "-accepthostname", "myhost.com"] + m_emulator.assert_called_with(True, 30.0, args_from_file) + + +@pytest.mark.usefixtures("mock_windows") +def test_open_connection_with_extra_args_multiline_comments(mocker: MockerFixture): + m_emulator = mocker.patch( + "Mainframe3270.py3270.Emulator.__init__", return_value=None + ) + mocker.patch("Mainframe3270.py3270.Emulator.connect") + extra_args = os.path.join(CURDIR, "resources/argfile_multiline_comments.txt") + + under_test = x3270(**X3270_DEFAULT_ARGS) + under_test.open_connection("myhost", extra_args=extra_args) + + args_from_file = ["-charset", "bracket", "-accepthostname", "myhost.com"] + m_emulator.assert_called_with(True, 30.0, args_from_file) + + def test_close_connection(mocker: MockerFixture, under_test: x3270): mocker.patch("Mainframe3270.py3270.Emulator.terminate") under_test.close_connection()