docs: def run(
      command,
      timeout=None,
      debug=False,
      stdin=None,
      print_stdout=True,
      print_stderr=True,
      callback_stdout=None,
      callback_stderr=None,
      environment=None,
      environment_override=None,
      win32resolve=True,
      working_directory=None,
  ):
      """
      Run an external process.
  
      File system path objects (PEP-519) are accepted in the command, environment,
      and working directory arguments.
  
      :param array command: Command line to be run, specified as array.
      :param timeout: Terminate program execution after this many seconds.
      :param boolean debug: Enable further debug messages.
      :param stdin: Optional string that is passed to command stdin.
      :param boolean print_stdout: Pass stdout through to sys.stdout.
      :param boolean print_stderr: Pass stderr through to sys.stderr.
      :param callback_stdout: Optional function which is called for each
                              stdout line.
      :param callback_stderr: Optional function which is called for each
                              stderr line.
      :param dict environment: The full execution environment for the command.
      :param dict environment_override: Change environment variables from the
                                        current values for command execution.
      :param boolean win32resolve: If on Windows, find the appropriate executable
                                   first. This allows running of .bat, .cmd, etc.
                                   files without explicitly specifying their
                                   extension.
      :param string working_directory: If specified, run the executable from
                                       within this working directory.
      :return: A ReturnObject() containing the executed command, stdout and stderr
               (both as bytestrings), and the exitcode. Further values such as
               process runtime can be accessed as dictionary values.
      """
  
      time_start = time.strftime("%Y-%m-%d %H:%M:%S GMT", time.gmtime())
      logger.debug("Starting external process: %s", command)
  
      if stdin is None:
          stdin_pipe = None
      else:
          assert sys.platform != "win32", "stdin argument not supported on Windows"
          stdin_pipe = subprocess.PIPE
  
      start_time = timeit.default_timer()
      if timeout is not None:
          max_time = start_time + timeout
  
      if environment is not None:
          env = {key: _path_resolve(environment[key]) for key in environment}
      else:
          env = os.environ
      if environment_override:
          env = copy.copy(env)
          env.update(
              {
                  key: str(_path_resolve(environment_override[key]))
                  for key in environment_override
              }
          )
  
      command = tuple(_path_resolve(part) for part in command)
      if win32resolve and sys.platform == "win32":
          command = _windows_resolve(command)
  
      p = subprocess.Popen(
          command,
          shell=False,
          cwd=_path_resolve(working_directory),
          env=env,
          stdin=stdin_pipe,
          stdout=subprocess.PIPE,
          stderr=subprocess.PIPE,
      )
  
      thread_pipe_pool = []
      notifyee, notifier = Pipe(False)
      thread_pipe_pool.append(notifyee)
      stdout = _NonBlockingStreamReader(
          p.stdout,
          output=print_stdout,
          debug=debug,
          notify=notifier.close,
          callback=callback_stdout,
      )
      notifyee, notifier = Pipe(False)
      thread_pipe_pool.append(notifyee)
      stderr = _NonBlockingStreamReader(
          p.stderr,
          output=print_stderr,
          debug=debug,
          notify=notifier.close,
          callback=callback_stderr,
      )
      if stdin is not None:
          notifyee, notifier = Pipe(False)
          thread_pipe_pool.append(notifyee)
          stdin = _NonBlockingStreamWriter(
              p.stdin, data=stdin, debug=debug, notify=notifier.close
          )
  
      timeout_encountered = False
  
      while (p.returncode is None) and (
          (timeout is None) or (timeit.default_timer() < max_time)
      ):
          if debug and timeout is not None:
              logger.debug("still running (T%.2fs)" % (timeit.default_timer() - max_time))
  
          # wait for some time or until a stream is closed
          try:
              if thread_pipe_pool:
                  # Wait for up to 0.5 seconds or for a signal on a remaining stream,
                  # which could indicate that the process has terminated.
                  try:
                      event = thread_pipe_pool[0].poll(0.5)
                  except IOError as e:
                      # on Windows this raises "IOError: [Errno 109] The pipe has been ended"
                      # which is for all intents and purposes equivalent to a True return value.
                      if e.errno != 109:
                          raise
                      event = True
                  if event:
                      # One-shot, so remove stream and watch remaining streams
                      thread_pipe_pool.pop(0)
                      if debug:
                          logger.debug("Event received from stream thread")
              else:
                  time.sleep(0.5)
          except KeyboardInterrupt:
              p.kill()  # if user pressed Ctrl+C we won't be able to produce a proper report anyway
              # but at least make sure the child process dies with us
              raise
  
          # check if process is still running
          p.poll()
  
      if p.returncode is None:
          # timeout condition
          timeout_encountered = True
          if debug:
              logger.debug("timeout (T%.2fs)" % (timeit.default_timer() - max_time))
  
          # send terminate signal and wait some time for buffers to be read
          p.terminate()
          if thread_pipe_pool:
              thread_pipe_pool[0].poll(0.5)
          if not stdout.has_finished() or not stderr.has_finished():
              time.sleep(2)
          p.poll()
  
      if p.returncode is None:
          # thread still alive
          # send kill signal and wait some more time for buffers to be read
          p.kill()
          if thread_pipe_pool:
              thread_pipe_pool[0].poll(0.5)
          if not stdout.has_finished() or not stderr.has_finished():
              time.sleep(5)
          p.poll()
  
      if p.returncode is None:
          raise RuntimeError("Process won't terminate")
  
      runtime = timeit.default_timer() - start_time
      if timeout is not None:
          logger.debug(
              "Process ended after %.1f seconds with exit code %d (T%.2fs)"
              % (runtime, p.returncode, timeit.default_timer() - max_time)
          )
      else:
          logger.debug(
              "Process ended after %.1f seconds with exit code %d"
              % (runtime, p.returncode)
          )
  
      stdout = stdout.get_output()
      stderr = stderr.get_output()
      time_end = time.strftime("%Y-%m-%d %H:%M:%S GMT", time.gmtime())
  
      result = ReturnObject(
          {
              "exitcode": p.returncode,
              "command": command,
              "stdout": stdout,
              "stderr": stderr,
              "timeout": timeout_encountered,
              "runtime": runtime,
              "time_start": time_start,
              "time_end": time_end,
          }
      )
      if stdin is not None:
          result.update(
              {
                  "stdin_bytes_sent": stdin.bytes_sent(),
                  "stdin_bytes_remain": stdin.bytes_remaining(),
              }
          )
  
      return result