text: import contextlib
  import io
  import json
  import os
  import shutil
  import subprocess
  from pathlib import Path
  from typing import Dict, Generator, List, Mapping, Optional, Tuple, Union
  
  import pytest
  from pkg_resources import Requirement, ResolutionError, resource_filename
  
  from cwltool.env_to_stdout import deserialize_env
  from cwltool.main import main
  from cwltool.singularity import is_version_2_6, is_version_3_or_newer
  
  
  def force_default_container(default_container_id: str, _: str) -> str:
      return default_container_id
  
  
  def get_data(filename: str) -> str:
      # normalizing path depending on OS or else it will cause problem when joining path
      filename = os.path.normpath(filename)
      filepath = None
      try:
          filepath = resource_filename(Requirement.parse("cwltool"), filename)
      except ResolutionError:
          pass
      if not filepath or not os.path.isfile(filepath):
          filepath = os.path.join(os.path.dirname(__file__), os.pardir, filename)
      return str(Path(filepath).resolve())
  
  
  needs_docker = pytest.mark.skipif(
      not bool(shutil.which("docker")),
      reason="Requires the docker executable on the system path.",
  )
  
  needs_singularity = pytest.mark.skipif(
      not bool(shutil.which("singularity")),
      reason="Requires the singularity executable on the system path.",
  )
  
  needs_singularity_2_6 = pytest.mark.skipif(
      not bool(shutil.which("singularity") and is_version_2_6()),
      reason="Requires that version 2.6.x of singularity executable version is on the system path.",
  )
  
  needs_singularity_3_or_newer = pytest.mark.skipif(
      (not bool(shutil.which("singularity"))) or (not is_version_3_or_newer()),
      reason="Requires that version 3.x of singularity executable version is on the system path.",
  )
  
  needs_podman = pytest.mark.skipif(
      not bool(shutil.which("podman")),
      reason="Requires the podman executable on the system path.",
  )
  
  _env_accepts_null: Optional[bool] = None
  
  
  def env_accepts_null() -> bool:
      """Return True iff the env command on this host accepts `-0`."""
      global _env_accepts_null
      if _env_accepts_null is None:
          result = subprocess.run(
              ["env", "-0"],
              capture_output=True,
              encoding="utf-8",
          )
          _env_accepts_null = result.returncode == 0
  
      return _env_accepts_null
  
  
  def get_main_output(
      args: List[str],
      replacement_env: Optional[Mapping[str, str]] = None,
      extra_env: Optional[Mapping[str, str]] = None,
      monkeypatch: Optional[pytest.MonkeyPatch] = None,
  ) -> Tuple[Optional[int], str, str]:
      """Run cwltool main.
  
      args: the command line args to call it with
  
      replacement_env: a total replacement of the environment
  
      extra_env: add these to the environment used
  
      monkeypatch: required if changing the environment
  
      Returns (return code, stdout, stderr)
      """
      stdout = io.StringIO()
      stderr = io.StringIO()
      if replacement_env is not None:
          assert monkeypatch is not None
          monkeypatch.setattr(os, "environ", replacement_env)
  
      if extra_env is not None:
          assert monkeypatch is not None
          for k, v in extra_env.items():
              monkeypatch.setenv(k, v)
  
      try:
          rc = main(argsl=args, stdout=stdout, stderr=stderr)
      except SystemExit as e:
          rc = e.code
      return (
          rc,
          stdout.getvalue(),
          stderr.getvalue(),
      )
  
  
  def get_tool_env(
      tmp_path: Path,
      flag_args: List[str],
      inputs_file: Optional[str] = None,
      replacement_env: Optional[Mapping[str, str]] = None,
      extra_env: Optional[Mapping[str, str]] = None,
      monkeypatch: Optional[pytest.MonkeyPatch] = None,
      runtime_env_accepts_null: Optional[bool] = None,
  ) -> Dict[str, str]:
      """Get the env vars for a tool's invocation."""
      # GNU env accepts the -0 option to end each variable's
      # printing with "\0". No such luck on BSD-ish.
      #
      # runtime_env_accepts_null is None => figure it out, otherwise
      # use wrapped bool (because containers).
      if runtime_env_accepts_null is None:
          runtime_env_accepts_null = env_accepts_null()
  
      args = flag_args.copy()
      if runtime_env_accepts_null:
          args.append(get_data("tests/env3.cwl"))
      else:
          args.append(get_data("tests/env4.cwl"))
  
      if inputs_file is not None:
          args.append(inputs_file)
  
      with working_directory(tmp_path):
          rc, stdout, _ = get_main_output(
              args,
              replacement_env=replacement_env,
              extra_env=extra_env,
              monkeypatch=monkeypatch,
          )
          assert rc == 0
  
          output = json.loads(stdout)
          with open(output["env"]["path"]) as _:
              return deserialize_env(_.read())
  
  
  @contextlib.contextmanager
  def working_directory(path: Union[str, Path]) -> Generator[None, None, None]:
      """Change working directory and returns to previous on exit."""
      prev_cwd = Path.cwd()
      os.chdir(path)
      try:
          yield
      finally:
          os.chdir(prev_cwd)
  