cpython icon indicating copy to clipboard operation
cpython copied to clipboard

gh-119273: Don't run test_ioctl in a process group

Open vstinner opened this issue 1 year ago • 1 comments

Python test runner no longer runs tests using TTY (ex: test_ioctl) in a process group (using setsid()). Previously, tests using TTY were skipped.

  • Issue: gh-119273

vstinner avatar May 20 '24 22:05 vstinner

Without this change, the test is skipped:

$ ./python -m test test_ioctl -j1
(...)
test_ioctl skipped -- Unable to open /dev/tty

With the change, the test is no longer skipped:

$ ./python -m test test_ioctl -j1
(...)
Result: SUCCESS

vstinner avatar May 20 '24 22:05 vstinner

Test on fork+setsid:

import io
import os

def check_tty(context):
    try:
        f = io.FileIO("/dev/tty", "a")
        tty = f.isatty()
        f.close()
    except OSError as exc:
        print(f"{context}: Is a TTY? {exc}")
    else:
        print(f"{context}: Is a TTY? {tty}")

check_tty("parent")
pid = os.fork()
if pid:
    os.waitpid(pid, 0)
else:
    os.setsid()
    check_tty("child after fork+setsid")

Result on Linux:

parent: Is a TTY? True
child after fork+setsid: Is a TTY? [Errno 6] No such device or address: '/dev/tty'

The child process cannot open /dev/tty after fork+setsid.

vstinner avatar May 23 '24 12:05 vstinner

The following tests contains the word tty (outside comments). I checked if they are skipped when run in a worker process.

Skipped tests:

  • test_builtin: always skipped. test_input_tty*() tests need a TTY and requires that readline is not loaded.
  • test_fileio: always skipped silently. testAbles()
  • test_ioctl: always skipped (whole file).
  • test_shutil: always skipped. test_shutil.test_stty_match() requires sys.__stdout__ to be a TTY to run the command stty size. When using worker process, stdout is a pipe and not a TTY.

Tests not skipped (ok):

  • test_curses
  • test_file
  • test_getpass: accessing /dev/tty uses a mock
  • test_os: stdin is a TTY.
  • test_pty
  • test_readline
  • test_sundry
  • test_threading
  • test_tty: use os.openpty()
  • test_utf8_mode

vstinner avatar May 23 '24 13:05 vstinner

Using os.openpty() is fine with fork+setsid:

import io
import os

def check_tty(context):
    try:
        parent_fd, child_fd = os.openpty()
        tty = os.isatty(parent_fd)
        os.close(parent_fd)
        os.close(child_fd)
    except OSError as exc:
        print(f"{context}: Is a TTY? {exc}")
    else:
        print(f"{context}: Is a TTY? {tty}")

check_tty("parent")
pid = os.fork()
if pid:
    os.waitpid(pid, 0)
else:
    os.setsid()
    check_tty("child after fork+setsid")

Output:

parent: Is a TTY? True
child after fork+setsid: Is a TTY? True

vstinner avatar May 27 '24 13:05 vstinner

I merged this PR as it is. I will prepare a following PR to handle test_builtin, test_fileio and test_shutil.

vstinner avatar May 29 '24 12:05 vstinner

test_builtin: always skipped. test_input_tty*() tests need a TTY and requires that readline is not loaded. test_shutil: always skipped. test_shutil.test_stty_match() requires sys.stdout to be a TTY to run the command stty size. When using worker process, stdout is a pipe and not a TTY.

Sadly, these two tests need stdout to be a TTY. It's only doable if tests are run sequentially.

test_fileio: always skipped silently. testAbles()

Well, it's just a subset of a minor test. I don't think that it's worth it to create a whole test file just for a few lines.

vstinner avatar May 29 '24 13:05 vstinner

Follow-up: I created issue gh-119727: Add --sequentially option to regrtest to always run tests sequentially (ignore -jN option).

vstinner avatar May 29 '24 13:05 vstinner