# Copyright Spack Project Developers. See COPYRIGHT file for details.
#
# SPDX-License-Identifier: (Apache-2.0 OR MIT)


import os
import pathlib

import pytest

import spack.llnl.util.filesystem as fs
import spack.platforms
from spack.hooks.absolutify_elf_sonames import SharedLibrariesVisitor, find_and_patch_sonames
from spack.util.executable import Executable


def skip_unless_linux(f):
    return pytest.mark.skipif(
        str(spack.platforms.real_host()) != "linux", reason="only tested on linux for now"
    )(f)


class ExecutableIntercept:
    def __init__(self):
        self.calls = []

    def __call__(self, *args, **kwargs):
        self.calls.append(args)

    @property
    def returncode(self):
        return 0


@pytest.mark.requires_executables("gcc")
@skip_unless_linux
def test_shared_libraries_visitor(tmp_path: pathlib.Path):
    """Integration test for soname rewriting"""
    gcc = Executable("gcc")

    # Create a directory structure like this:
    # ./no-soname.so                        # just a shared library without a soname
    # ./soname.so                           # a shared library with a soname
    # ./executable.so                       # an executable masquerading as a shared lib
    # ./libskipme.so                        # a shared library with a soname
    # ./mydir/parent_dir -> ..              # a symlinked dir, causing a cycle
    # ./mydir/skip_symlink -> ../libskipme  # a symlink to a library

    with fs.working_dir(str(tmp_path)):
        with open("hello.c", "w", encoding="utf-8") as f:
            f.write("int main(){return 0;}")
        gcc("hello.c", "-o", "no-soname.so", "--shared")
        gcc("hello.c", "-o", "soname.so", "--shared", "-Wl,-soname,example.so")
        gcc("hello.c", "-pie", "-o", "executable.so")
        gcc("hello.c", "-o", "libskipme.so", "-Wl,-soname,libskipme.so")
        os.mkdir("my_dir")
        os.symlink("..", os.path.join("my_dir", "parent_dir"))
        os.symlink(os.path.join("..", "libskipme.so"), os.path.join("my_dir", "skip_symlink"))

    # Visit the whole prefix, but exclude `skip_symlink`
    visitor = SharedLibrariesVisitor(exclude_list=["skip_symlink"])
    fs.visit_directory_tree(str(tmp_path), visitor)
    relative_paths = visitor.get_shared_libraries_relative_paths()

    assert "no-soname.so" in relative_paths
    assert "soname.so" in relative_paths
    assert "executable.so" not in relative_paths
    assert "libskipme.so" not in relative_paths

    # Run the full hook of finding libs and setting sonames.
    patchelf = ExecutableIntercept()
    find_and_patch_sonames(str(tmp_path), ["skip_symlink"], patchelf)
    assert len(patchelf.calls) == 2
    elf_1 = tmp_path / "no-soname.so"
    elf_2 = tmp_path / "soname.so"
    assert ("--set-soname", str(elf_1), str(elf_1)) in patchelf.calls
    assert ("--set-soname", str(elf_2), str(elf_2)) in patchelf.calls
