Exemple #1
0
def test_runtime_dir_uses_tmp_fallback():
    # Given XDG_RUNTIME_DIR and TMPDIR are not set
    # When RuntimeDir is invoked with name 'quicken-test'
    # Then it should succeed and its path should be
    #   /tmp/quicken-test-{uid}
    with env(XDG_RUNTIME_DIR=None, TMPDIR=None):
        runtime_dir = RuntimeDir("quicken-test")
        uid = os.getuid()
        assert str(runtime_dir) == f"/tmp/quicken-test-{uid}"
        os.fchdir(runtime_dir.fileno())
        assert str(runtime_dir) == os.getcwd()
Exemple #2
0
def test_runtime_dir_uses_xdg_env_var():
    # Given XDG_RUNTIME_DIR is set
    # When RuntimeDir is invoked with name 'quicken-test'
    # Then it should succeed and its path should be
    #   $XDG_RUNTIME_DIR/quicken-test.
    with tempfile.TemporaryDirectory() as p:
        with env(XDG_RUNTIME_DIR=p):
            runtime_dir = RuntimeDir("quicken-test")
            assert str(runtime_dir) == os.path.join(p, "quicken-test")
            os.fchdir(runtime_dir.fileno())
            assert str(runtime_dir) == os.getcwd()
Exemple #3
0
def test_runtime_dir_uses_tmpdir_env_var():
    # Given XDG_RUNTIME_DIR is not set
    # And TMPDIR is set
    # When RuntimeDir is invoked with name 'quicken-test'
    # Then it should succeed and its path should be
    #   $TMPDIR/quicken-test-{uid}
    with tempfile.TemporaryDirectory() as p:
        with env(XDG_RUNTIME_DIR=None, TMPDIR=p):
            runtime_dir = RuntimeDir("quicken-test")
            uid = os.getuid()
            assert str(runtime_dir) == os.path.join(p, f"quicken-test-{uid}")
            os.fchdir(runtime_dir.fileno())
            assert str(runtime_dir) == os.getcwd()
Exemple #4
0
def test_runtime_dir_succeeds_creating_a_file():
    sample_text = "hello"
    with tempfile.TemporaryDirectory() as p:
        runtime_dir = RuntimeDir(dir_path=p)
        file = get_bound_path(runtime_dir, "example")
        file.write_text(sample_text, encoding="utf-8")
        text = (Path(p) / "example").read_text(encoding="utf-8")
        assert sample_text == text
Exemple #5
0
def test_runtime_dir_fails_when_bad_permissions():
    # Given a directory that exists with permissions 770.
    # When a RuntimeDir is constructed from it then
    with tempfile.TemporaryDirectory() as p:
        os.chmod(p, 0o770)
        with pytest.raises(RuntimeError) as excinfo:
            _runtime_dir = RuntimeDir(dir_path=p)
        v = str(excinfo.value)
        assert "must have permissions 700" in v
Exemple #6
0
def test_killed_client_causes_handler_to_exit():
    # Given the server is processing a command in a subprocess.
    # And the client process is killed (receives SIGKILL and exits)
    # Then the same signal should be sent to the subprocess running the command
    # And it should exit.
    @cli_factory()
    def runner():
        def inner():
            # Block just to ensure that only an unblockable signal would
            # be able terminate the process.
            signal.pthread_sigmask(signal.SIG_BLOCK, forwarded_signals)
            pid = os.getpid()
            fd, path = tempfile.mkstemp()
            os.write(fd, str(pid).encode("utf-8"))
            os.fsync(fd)
            os.close(fd)
            os.rename(path, runner_pid_file)
            logger.debug("Inner function waiting")
            while True:
                signal.pause()

        return inner

    # Client runs in child process so we don't kill the test process itself.
    def client():
        logger.debug("Client starting")
        signal.pthread_sigmask(signal.SIG_BLOCK, forwarded_signals)
        sys.exit(runner())

    with isolated_filesystem() as path:
        with contained_children():
            runner_pid_file = Path("runner_pid").absolute()
            runtime_dir = RuntimeDir(dir_path=str(path))
            p = Process(target=client)
            logger.debug("Starting process")
            p.start()

            logger.debug("Waiting for pid file")
            assert wait_for_create(
                get_bound_path(runtime_dir, runner_pid_file.name),
                timeout=2), f"{runner_pid_file} must have been created"
            runner_pid = int(runner_pid_file.read_text(encoding="utf-8"))
            logger.debug("Runner started with pid: %d", runner_pid)

            client_process = psutil.Process(pid=p.pid)
            runner_process = psutil.Process(pid=runner_pid)

            logger.debug("Killing client")
            client_process.kill()
            logger.debug("Waiting for client")
            p.join()
            logger.debug("Waiting for runner")
            runner_process.wait()
Exemple #7
0
def test_runtime_dir_path_fails_when_directory_unlinked_and_recreated():
    # Given a runtime dir that has been created.
    # And removed
    # And recreated manually
    # When the runtime dir is used to create a new file
    # Then the operation should fail.
    sample_text = "hello"
    with tempfile.TemporaryDirectory() as p:
        runtime_dir = RuntimeDir(dir_path=p)
        file = get_bound_path(runtime_dir, "example")

    Path(p).mkdir()

    try:
        with pytest.raises(FileNotFoundError) as excinfo:
            file.write_text(sample_text, encoding="utf-8")

        assert "example" in str(excinfo.value)
        assert Path(p).exists()
    finally:
        Path(p).rmdir()
Exemple #8
0
def test_watch_for_create_notices_file_fast():
    with isolated_filesystem() as p:
        # To rule out dependence on being in the cwd.
        os.chdir("/")
        runtime_dir = RuntimeDir(dir_path=p)
        file = get_bound_path(runtime_dir, "example.txt")
        writer_timestamp: datetime = None

        def create_file():
            nonlocal writer_timestamp
            time.sleep(0.05)
            file.write_text("hello", encoding="utf-8")
            writer_timestamp = datetime.now()

        t = threading.Thread(target=create_file)
        t.start()
        result = wait_for_create(file, timeout=1)
        t.join()
        timestamp = datetime.now()
        assert result, "File must have been created"
        assert timestamp - writer_timestamp < timedelta(seconds=0.05)
Exemple #9
0
def test_client_receiving_tstp_ttin_stops_itself():
    # Given the server is processing a command in a subprocess
    # When the client receives signal.SIGTSTP or signal.SIGTTIN
    # Then the same signal should be sent to the subprocess running the command
    # And the client should be stopped

    # Ensure that the test runner caller doesn't impact signal handling.
    signal.pthread_sigmask(signal.SIG_SETMASK, [])

    test_signals = {signal.SIGTSTP, signal.SIGTTIN}
    resume_signal = signal.SIGUSR1

    @cli_factory()
    def runner():
        def inner():
            # Block signals we expect to receive
            signal.pthread_sigmask(signal.SIG_BLOCK,
                                   test_signals | {signal.SIGUSR1})
            # Save our pid so it is accessible to the test process, avoiding
            # any race conditions where the file may be empty.
            pid = os.getpid()
            fd, path = tempfile.mkstemp()
            os.write(fd, str(pid).encode("utf-8"))
            os.fsync(fd)
            os.close(fd)
            os.rename(path, runner_pid_file)

            for sig in test_signals:
                logger.debug("Waiting for %s", sig)
                signal.sigwait({sig})
                # Stop self to indicate success to test process.
                os.kill(pid, signal.SIGSTOP)

            logger.debug("Waiting for signal to exit")
            # This is required otherwise we may exit while the test is checking
            # for our status.
            signal.sigwait({resume_signal})
            logger.debug("All signals received")

        return inner

    def client():
        # All the work to forward signals is done in the library.
        sys.exit(runner())

    def wait_for(predicate):
        # Busy wait since we don't have a good way to get signalled on process
        # status change.
        while not predicate():
            time.sleep(0.1)

    with isolated_filesystem() as path:
        with contained_children():
            # Get process pids. The Process object already has the client pid,
            # but we need to wait for the runner pid to be written to the file.
            runner_pid_file = Path("runner_pid").absolute()
            runtime_dir = RuntimeDir(dir_path=str(path))
            p = Process(target=client)
            p.start()
            assert wait_for_create(
                get_bound_path(runtime_dir, runner_pid_file.name),
                timeout=2), f"{runner_pid_file} must have been created"
            runner_pid = int(runner_pid_file.read_text(encoding="utf-8"))

            # Stop and continue the client process, checking that it was
            # correctly applied to both the client and runner processes.
            client_process = psutil.Process(pid=p.pid)
            runner_process = psutil.Process(pid=runner_pid)
            for sig in [signal.SIGTSTP, signal.SIGTTIN]:
                logger.debug("Sending %s", sig)
                client_process.send_signal(sig)
                logger.debug("Waiting for client to stop")
                wait_for(
                    lambda: client_process.status() == psutil.STATUS_STOPPED)
                logger.debug("Waiting for runner to stop")
                wait_for(
                    lambda: runner_process.status() == psutil.STATUS_STOPPED)

                client_process.send_signal(signal.SIGCONT)
                logger.debug("Waiting for client to resume")
                wait_for(
                    lambda: client_process.status() != psutil.STATUS_STOPPED)
                logger.debug("Waiting for runner to resume")
                wait_for(
                    lambda: runner_process.status() != psutil.STATUS_STOPPED)

            # Resume runner process so it exits.
            runner_process.send_signal(resume_signal)

            logger.debug("Waiting for client to finish")
            p.join()
            assert p.exitcode == 0
Exemple #10
0
def test_runtime_dir_fails_when_no_args():
    with pytest.raises(ValueError) as excinfo:
        _runtime_dir = RuntimeDir()
    v = str(excinfo.value)
    assert "base_name" in v and "dir_path" in v
Exemple #11
0
def test_wait_for_delete_fails_existing_file():
    with isolated_filesystem() as p:
        runtime_dir = RuntimeDir(dir_path=p)
        file = get_bound_path(runtime_dir, "example.txt")
        file.write_text("hello", encoding="utf-8")
        assert not wait_for_delete(file, timeout=0.1)
Exemple #12
0
def test_wait_for_delete_notices_missing_file():
    with isolated_filesystem() as p:
        runtime_dir = RuntimeDir(dir_path=p)
        file = get_bound_path(runtime_dir, "example.txt")
        assert wait_for_delete(file, timeout=0.01)