# Copyright 1998-2020, 2023 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import functools
import os
import pty
import shutil
import socket
import tempfile

import pytest
from _emerge.PipeReader import PipeReader

from portage.tests import TestCase
from portage.util._eventloop.global_event_loop import global_event_loop
from portage.util.futures import asyncio


class PipeReaderTestCase(TestCase):
    _use_array = False
    _echo_cmd = "echo -n '%s'"

    def test_pipe(self):
        def make_pipes():
            return os.pipe(), None

        self._do_test(make_pipes)

    def test_pty_device(self):
        def make_pipes():
            try:
                return pty.openpty(), None
            except OSError:
                self.skipTest("pty not available")

        self._do_test(make_pipes)

    def test_domain_socket(self):
        def make_pipes():
            read_end, write_end = socket.socketpair()
            return (read_end.detach(), write_end.detach()), None

        self._do_test(make_pipes)

    def test_named_pipe(self):
        def make_pipes():
            tempdir = tempfile.mkdtemp()
            fifo_path = os.path.join(tempdir, "fifo")
            os.mkfifo(fifo_path)
            return (
                (
                    os.open(fifo_path, os.O_NONBLOCK | os.O_RDONLY),
                    os.open(fifo_path, os.O_NONBLOCK | os.O_WRONLY),
                ),
                functools.partial(shutil.rmtree, tempdir),
            )

        self._do_test(make_pipes)

    def _testPipeReader(self, master_fd, slave_fd, test_string):
        """
        Use a poll loop to read data from a pipe and assert that
        the data written to the pipe is identical to the data
        read from the pipe.
        """

        # WARNING: It is very important to use unbuffered mode here,
        # in order to avoid issue 5380 with python3.
        master_file = os.fdopen(master_fd, "rb", 0)
        scheduler = global_event_loop()

        consumer = PipeReader(
            input_files={"producer": master_file},
            _use_array=self._use_array,
            scheduler=scheduler,
        )
        consumer.start()

        producer = scheduler.run_until_complete(
            asyncio.create_subprocess_exec(
                "bash",
                "-c",
                self._echo_cmd % test_string,
                stdout=slave_fd,
                loop=scheduler,
            )
        )

        os.close(slave_fd)
        scheduler.run_until_complete(producer.wait())
        scheduler.run_until_complete(consumer.async_wait())

        self.assertEqual(producer.returncode, os.EX_OK)
        self.assertEqual(consumer.returncode, os.EX_OK)

        return consumer.getvalue().decode("ascii", "replace")

    def _do_test(self, make_pipes):
        for x in (1, 2, 5, 6, 7, 8, 2**5, 2**10, 2**12, 2**13, 2**14):
            test_string = x * "a"
            (read_end, write_end), cleanup = make_pipes()
            try:
                output = self._testPipeReader(read_end, write_end, test_string)
                self.assertEqual(
                    test_string,
                    output,
                    f"x = {x}, len(output) = {len(output)}",
                )
            finally:
                if cleanup is not None:
                    cleanup()


@pytest.mark.xfail()  # This fails sometimes, that's the reason of xfail here
class PipeReaderArrayTestCase(PipeReaderTestCase):
    _use_array = True
    # sleep allows reliable triggering of the failure mode on fast computers
    _echo_cmd = "sleep 0.1 ; echo -n '%s'"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # https://bugs.python.org/issue5380
        # https://bugs.pypy.org/issue956
