Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Open connection port in args rework #2

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}

Expand Down Expand Up @@ -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
Expand All @@ -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/
113 changes: 47 additions & 66 deletions Mainframe3270/py3270.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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):
Expand All @@ -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
Expand Down
29 changes: 19 additions & 10 deletions Mainframe3270/x3270.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 |
Expand All @@ -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}")
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
14 changes: 14 additions & 0 deletions utest/conftest.py
Original file line number Diff line number Diff line change
@@ -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")
Loading