# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Copyright (c) 2014-2021, Lars Asplund lars.anders.asplund@gmail.com

"""
Test the ModelSim interface
"""


import unittest
from pathlib import Path
import os
from shutil import rmtree
from unittest import mock
from tests.common import set_env
from vunit.sim_if.modelsim import ModelSimInterface
from vunit.project import Project
from vunit.ostools import renew_path, write_file
from vunit.vhdl_standard import VHDL


class TestModelSimInterface(unittest.TestCase):
    """
    Test the ModelSim interface
    """

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_vhdl_2008(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.vhd", "")
        project.add_source_file("file.vhd", "lib", file_type="vhdl", vhdl_standard=VHDL.standard("2008"))
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vcom"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-2008",
            "-work",
            "lib",
            "file.vhd",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_vhdl_2002(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.vhd", "")
        project.add_source_file("file.vhd", "lib", file_type="vhdl", vhdl_standard=VHDL.standard("2002"))
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vcom"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-2002",
            "-work",
            "lib",
            "file.vhd",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_vhdl_93(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.vhd", "")
        project.add_source_file("file.vhd", "lib", file_type="vhdl", vhdl_standard=VHDL.standard("93"))
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vcom"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-93",
            "-work",
            "lib",
            "file.vhd",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_vhdl_extra_flags(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.vhd", "")
        source_file = project.add_source_file("file.vhd", "lib", file_type="vhdl")
        source_file.set_compile_option("modelsim.vcom_flags", ["custom", "flags"])
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vcom"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "custom",
            "flags",
            "-2008",
            "-work",
            "lib",
            "file.vhd",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_verilog(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.v", "")
        project.add_source_file("file.v", "lib", file_type="verilog")
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vlog"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-work",
            "lib",
            "file.v",
            "-L",
            "lib",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_system_verilog(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.sv", "")
        project.add_source_file("file.sv", "lib", file_type="systemverilog")
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vlog"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-sv",
            "-work",
            "lib",
            "file.sv",
            "-L",
            "lib",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_verilog_extra_flags(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.v", "")
        source_file = project.add_source_file("file.v", "lib", file_type="verilog")
        source_file.set_compile_option("modelsim.vlog_flags", ["custom", "flags"])
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vlog"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "custom",
            "flags",
            "-work",
            "lib",
            "file.v",
            "-L",
            "lib",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_verilog_include(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.v", "")
        project.add_source_file("file.v", "lib", file_type="verilog", include_dirs=["include"])
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        check_args = [
            str(Path(self.prefix_path) / "vlog"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-work",
            "lib",
            "file.v",
            "-L",
            "lib",
            "+incdir+include",
        ]
        check_output.assert_called_once_with(check_args, env=simif.get_env())

    @mock.patch("vunit.sim_if.check_output", autospec=True, return_value="")
    @mock.patch("vunit.sim_if.modelsim.Process", autospec=True)
    def test_compile_project_verilog_define(self, process, check_output):
        simif = ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        project = Project()
        project.add_library("lib", "lib_path")
        write_file("file.v", "")
        project.add_source_file("file.v", "lib", file_type="verilog", defines={"defname": "defval"})
        simif.compile_project(project)
        process_args = [str(Path(self.prefix_path) / "vlib"), "-unix", "lib_path"]
        process.assert_called_once_with(process_args, env=simif.get_env())
        process_args = [
            str(Path(self.prefix_path) / "vlog"),
            "-quiet",
            "-modelsimini",
            str(Path(self.output_path) / "modelsim.ini"),
            "-work",
            "lib",
            "file.v",
            "-L",
            "lib",
            "+define+defname=defval",
        ]
        check_output.assert_called_once_with(process_args, env=simif.get_env())

    def _get_inis(self):
        return (
            str(Path(self.output_path) / "modelsim.ini"),
            str(Path(self.prefix_path) / ".." / "modelsim.ini"),
            str(Path(self.test_path) / "my_modelsim.ini"),
        )

    def test_copies_modelsim_ini_file_from_install(self):
        (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis()

        with open(installed_modelsim_ini, "w") as fptr:
            fptr.write("installed")

        with open(user_modelsim_ini, "w") as fptr:
            fptr.write("user")

        ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        with open(modelsim_ini, "r") as fptr:
            self.assertEqual(fptr.read(), "installed")

    def test_copies_modelsim_ini_file_from_user(self):
        (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis()

        with open(installed_modelsim_ini, "w") as fptr:
            fptr.write("installed")

        with open(user_modelsim_ini, "w") as fptr:
            fptr.write("user")

        with set_env(VUNIT_MODELSIM_INI=user_modelsim_ini):
            ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)

        with open(modelsim_ini, "r") as fptr:
            self.assertEqual(fptr.read(), "user")

    def test_overwrites_modelsim_ini_file_from_install(self):
        (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis()

        with open(modelsim_ini, "w") as fptr:
            fptr.write("existing")

        with open(installed_modelsim_ini, "w") as fptr:
            fptr.write("installed")

        with open(user_modelsim_ini, "w") as fptr:
            fptr.write("user")

        ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)
        with open(modelsim_ini, "r") as fptr:
            self.assertEqual(fptr.read(), "installed")

    def test_overwrites_modelsim_ini_file_from_user(self):
        (modelsim_ini, installed_modelsim_ini, user_modelsim_ini) = self._get_inis()

        with open(modelsim_ini, "w") as fptr:
            fptr.write("existing")

        with open(installed_modelsim_ini, "w") as fptr:
            fptr.write("installed")

        with open(user_modelsim_ini, "w") as fptr:
            fptr.write("user")

        with set_env(VUNIT_MODELSIM_INI=user_modelsim_ini):
            ModelSimInterface(prefix=self.prefix_path, output_path=self.output_path, persistent=False)

        with open(modelsim_ini, "r") as fptr:
            self.assertEqual(fptr.read(), "user")

    def setUp(self):
        self.test_path = str(Path(__file__).parent / "test_modelsim_out")

        self.output_path = str(Path(self.test_path) / "modelsim")
        self.prefix_path = str(Path(self.test_path) / "prefix" / "bin")
        renew_path(self.test_path)
        renew_path(self.output_path)
        renew_path(self.prefix_path)
        installed_modelsim_ini = str(Path(self.prefix_path) / ".." / "modelsim.ini")
        write_file(installed_modelsim_ini, "[Library]")
        self.project = Project()
        self.cwd = os.getcwd()
        os.chdir(self.test_path)

    def tearDown(self):
        os.chdir(self.cwd)
        if Path(self.test_path).exists():
            rmtree(self.test_path)
