# Copyright 2024 The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""Unit tests for the QemuExecutor class."""
from pathlib import Path
from unittest import TestCase, mock
from unittest.mock import MagicMock

from debusine.client.debusine import Debusine
from debusine.tasks import Noop
from debusine.tasks.executors.base import (
    ImageImportError,
    ImageNotDownloadedError,
    executor,
)
from debusine.tasks.executors.qemu import QemuExecutor
from debusine.tasks.tests.helper_mixin import ExternalTaskHelperMixin
from debusine.test import TestHelpersMixin


class QemuExecutorTests(
    TestHelpersMixin, ExternalTaskHelperMixin[Noop], TestCase
):
    """Unit tests for QemuExecutor."""

    def setUp(self):
        """Mock the Debusine API for tests."""
        self.debusine_api = MagicMock(spec=Debusine)
        self.image_artifact = self.mock_image_download(
            self.debusine_api, system_image=True
        )
        self.image_artifact.data["pkglist"]["python3"] = "3.0"

        self.executor = QemuExecutor(self.debusine_api, 42)

    def test_backend_name(self):
        """Test that the backend_name attribute was set."""
        self.assertEqual(QemuExecutor.backend_name, "qemu")

    @mock.patch("platform.machine")
    @mock.patch("os.access")
    def test_available(self, access, machine):
        """available() returns True if qemu and /dev/kvm are available."""
        access.return_value = True
        machine.return_value = "x86_64"
        self.mock_is_command_available({"qemu-system-x86_64": True})
        self.assertTrue(QemuExecutor.available())

    @mock.patch("platform.machine")
    @mock.patch("os.access")
    def test_available_no_qemu(self, access, machine):
        """Test that available() returns False if qemu is not available."""
        access.return_value = True
        machine.return_value = "x86_64"
        self.mock_is_command_available({"qemu-system-x86_64": False})
        self.assertFalse(QemuExecutor.available())

    @mock.patch("platform.machine")
    @mock.patch("os.access")
    def test_available_no_kvm(self, access, machine):
        """Test that available() returns False if kvm is not available."""
        access.return_value = False
        machine.return_value = "x86_64"
        self.mock_is_command_available({"qemu-system-x86_64": True})
        self.assertFalse(QemuExecutor.available())

    def test_instantiation_fetches_artifact(self):
        """Test that instantiating QemuExecutor fetches the artifact."""
        self.assertEqual(self.executor.system_image, self.image_artifact)

    def test_download_image(self):
        """Test that download_image calls ImageCache.download_image."""
        response = self.executor.download_image()
        expected_path = self.image_cache_path / "42/image.qcow2"
        self.assertEqual(self.executor._local_path, expected_path)
        self.assertEqual(response, str(expected_path))

    def test_download_image_requires_python3(self):
        """Test that download_image requires python3 in the image."""
        del self.image_artifact.data["pkglist"]["python3"]
        with self.assertRaisesRegex(
            ImageImportError,
            "Image doesn't contain an python3, required by qemu backend.",
        ):
            self.executor.download_image()

    def test_image_name(self):
        """Test that image_name returns the previously downloaded image path."""
        self.executor.download_image()
        response = self.executor.image_name()
        expected_path = self.image_cache_path / "42/image.qcow2"
        self.assertEqual(response, str(expected_path))

    def test_image_name_not_downloaded(self):
        """Test that image_name fails if there is no name."""
        with self.assertRaisesRegex(
            ImageNotDownloadedError, r"download_image\(\) not yet called"
        ):
            self.executor.image_name()

    def test_autopkgtest_virt_server(self):
        """Test that autopkgtest_virt_server returns qemu."""
        self.assertEqual(self.executor.autopkgtest_virt_server(), "qemu")

    def test_autopkgtest_virt_args(self):
        """Test that autopkgtest_virt_args returns sane arguments."""
        self.executor._local_path = Path("/not/used/path/image.qcow2")
        self.assertEqual(
            self.executor.autopkgtest_virt_args(),
            [
                "--boot",
                "efi",
                "--dpkg-architecture",
                "amd64",
                str(self.executor._local_path),
            ],
        )

    def test_autopkgtest_virt_args_before_download(self):
        """Test that autopkgtest_virt_args requires download."""
        with self.assertRaisesRegex(
            ImageNotDownloadedError, r"download_image\(\) not yet called"
        ):
            self.executor.autopkgtest_virt_args()

    def test_executor_instantiates_qemu(self):
        """Test that executor() supports qemu."""
        instance = executor(self.debusine_api, "qemu", 42)
        self.assertIsInstance(instance, QemuExecutor)

    def test_create(self):
        """Test create() raises NotImplemented."""
        with self.assertRaises(NotImplementedError):
            self.executor.create()
