zhmcclient / zhmccli

A CLI for the IBM Z HMC
Apache License 2.0
9 stars 9 forks source link

Make end2end tests run against a mocked environment #413

Open andy-maier opened 1 year ago

andy-maier commented 1 year ago

With PR #411 , the following error is surfaced (using a locally defined mock file, on Python 3.9 on macOS):

$ TESTHMC=mocked_z13_dpm make end2end
find . -type d -name 'htmlcov.end2end' | xargs -n 1 rm -rf
bash -c "TESTEND2END_LOAD=true py.test --color=yes  -v -s tests/end2end --cov zhmccli --cov-config .coveragerc.end2end --cov-report=html "
================================================================================= test session starts ==================================================================================
platform darwin -- Python 3.9.16, pytest-7.3.1, pluggy-1.0.0 -- /Users/maiera/virtualenvs/zhmccli39/bin/python
cachedir: .pytest_cache
rootdir: /Users/maiera/Projects/zhmcclient/repos/zhmccli
plugins: cov-4.0.0
collected 1 item                                                                                                                                                                       

tests/end2end/test_session.py::test_session_success[hmc_definition=mocked_z13_dpm] FAILED/Users/maiera/virtualenvs/zhmccli39/lib/python3.9/site-packages/coverage/control.py:801: CoverageWarning: No data was collected. (no-data-collected)
  self._warn("No data was collected.", slug="no-data-collected")

======================================================================================= FAILURES =======================================================================================
_________________________________________________________________ test_session_success[hmc_definition=mocked_z13_dpm] __________________________________________________________________

hmc_definition = HMCDefinition(nickname='mocked_z13_dpm', description='Mocked z13 in DPM mode', contact='', access_via='', mock_file='/...y_cert=True, cpcs=OrderedDict([('CPC1', OrderedDict([('machine_type', '2964'), ('dpm_enabled', True)]))]), add_vars={})

    def test_session_success(hmc_definition):  # noqa: F811
        # pylint: disable=redefined-outer-name
        """
        Test successful creation and deletion of an HMC session.
        """

        # Create the session

        # Set to True to debug 'zhmc session create'
        pdb = False

        zhmc_args = zhmc_session_args(hmc_definition) + ['session', 'create']

        # The code to be tested
>       rc, stdout, stderr = run_zhmc(zhmc_args, pdb=pdb)

tests/end2end/test_session.py:48: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/end2end/utils.py:103: in run_zhmc
    proc = subprocess.Popen(p_args, env=env, **kwargs)
/usr/local/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/subprocess.py:951: in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Popen: returncode: None args: ['zhmc', '-h', None, '-u', 'None', '-p', 'Non...>, args = ['zhmc', '-h', None, '-u', 'None', '-p', ...], executable = b'zhmc', preexec_fn = None
close_fds = True, pass_fds = (), cwd = None, env = None, startupinfo = None, creationflags = 0, shell = False, p2cread = -1, p2cwrite = -1, c2pread = 5, c2pwrite = 6, errread = 7
errwrite = 8, restore_signals = True, gid = None, gids = None, uid = None, umask = -1, start_new_session = False

    def _execute_child(self, args, executable, preexec_fn, close_fds,
                       pass_fds, cwd, env,
                       startupinfo, creationflags, shell,
                       p2cread, p2cwrite,
                       c2pread, c2pwrite,
                       errread, errwrite,
                       restore_signals,
                       gid, gids, uid, umask,
                       start_new_session):
        """Execute program (POSIX version)"""

        if isinstance(args, (str, bytes)):
            args = [args]
        elif isinstance(args, os.PathLike):
            if shell:
                raise TypeError('path-like args is not allowed when '
                                'shell is true')
            args = [args]
        else:
            args = list(args)

        if shell:
            # On Android the default shell is at '/system/bin/sh'.
            unix_shell = ('/system/bin/sh' if
                      hasattr(sys, 'getandroidapilevel') else '/bin/sh')
            args = [unix_shell, "-c"] + args
            if executable:
                args[0] = executable

        if executable is None:
            executable = args[0]

        sys.audit("subprocess.Popen", executable, args, cwd, env)

        if (_USE_POSIX_SPAWN
                and os.path.dirname(executable)
                and preexec_fn is None
                and not close_fds
                and not pass_fds
                and cwd is None
                and (p2cread == -1 or p2cread > 2)
                and (c2pwrite == -1 or c2pwrite > 2)
                and (errwrite == -1 or errwrite > 2)
                and not start_new_session
                and gid is None
                and gids is None
                and uid is None
                and umask < 0):
            self._posix_spawn(args, executable, env, restore_signals,
                              p2cread, p2cwrite,
                              c2pread, c2pwrite,
                              errread, errwrite)
            return

        orig_executable = executable

        # For transferring possible exec failure from child to parent.
        # Data format: "exception name:hex errno:description"
        # Pickle is not used; it is complex and involves memory allocation.
        errpipe_read, errpipe_write = os.pipe()
        # errpipe_write must not be in the standard io 0, 1, or 2 fd range.
        low_fds_to_close = []
        while errpipe_write < 3:
            low_fds_to_close.append(errpipe_write)
            errpipe_write = os.dup(errpipe_write)
        for low_fd in low_fds_to_close:
            os.close(low_fd)
        try:
            try:
                # We must avoid complex work that could involve
                # malloc or free in the child process to avoid
                # potential deadlocks, thus we do all this here.
                # and pass it to fork_exec()

                if env is not None:
                    env_list = []
                    for k, v in env.items():
                        k = os.fsencode(k)
                        if b'=' in k:
                            raise ValueError("illegal environment variable name")
                        env_list.append(k + b'=' + os.fsencode(v))
                else:
                    env_list = None  # Use execv instead of execve.
                executable = os.fsencode(executable)
                if os.path.dirname(executable):
                    executable_list = (executable,)
                else:
                    # This matches the behavior of os._execvpe().
                    executable_list = tuple(
                        os.path.join(os.fsencode(dir), executable)
                        for dir in os.get_exec_path(env))
                fds_to_keep = set(pass_fds)
                fds_to_keep.add(errpipe_write)
>               self.pid = _posixsubprocess.fork_exec(
                        args, executable_list,
                        close_fds, tuple(sorted(map(int, fds_to_keep))),
                        cwd, env_list,
                        p2cread, p2cwrite, c2pread, c2pwrite,
                        errread, errwrite,
                        errpipe_read, errpipe_write,
                        restore_signals, start_new_session,
                        gid, gids, uid, umask,
                        preexec_fn)
E                       TypeError: expected str, bytes or os.PathLike object, not NoneType

/usr/local/Cellar/python@3.9/3.9.16/Frameworks/Python.framework/Versions/3.9/lib/python3.9/subprocess.py:1754: TypeError

---------- coverage: platform darwin, python 3.9.16-final-0 ----------
Coverage HTML written to dir htmlcov.end2end

=============================================================================== short test summary info ================================================================================
FAILED tests/end2end/test_session.py::test_session_success[hmc_definition=mocked_z13_dpm] - TypeError: expected str, bytes or os.PathLike object, not NoneType
================================================================================== 1 failed in 2.12s ===================================================================================
make: *** [end2end] Error 1
andy-maier commented 9 months ago

Initial analysis shows that create_hmc_session() only supports real HMCs, because it dos not create FakedSession objects for mocked HMCs. See zhmcclient fixture hmc_session() for how it is done.

However, since delete_hmc_session() and is_valid_hmc_session() depend on working session IDs, we first need to add proper session ID support to the zhmcclient_mock support. Created https://github.com/zhmcclient/python-zhmcclient/issues/1437 for that.

andy-maier commented 8 months ago

PR https://github.com/zhmcclient/python-zhmcclient/pull/1456 targeted for zhmcclient 1.15.0 provides dynamically created session IDs, and the ability in the FakedHmc class to validate, add and remove session IDs. That should allow to resolve this issue here.