import subprocess
import time

import pytest

from tests import dds, DDS, dds_fixture_conf_1
from dds_ci import proc

## #############################################################################
## #############################################################################
## The test project in this directory contains a single application and two
## functions, each defined in a separate file. The two functions each return
## an integer, and the application exit code will be the difference between
## the two integers. (They are passed through std::abs(), so it is always a
## positive integer). The default value is 32 in both functions.
## #############################################################################
## The purpose of these tests is to ensure the reliability of the compilation
## dependency database. Having a miscompile because there was a failure to
## detect file changes is a catastrophic bug!


def build_and_get_rc(dds: DDS) -> int:
    dds.build()
    app = dds.build_dir / ('app' + dds.exe_suffix)
    return proc.run(app).returncode


def test_simple_rebuild(dds: DDS):
    """
    Check that changing a source file will update the resulting application.
    """
    assert build_and_get_rc(dds) == 0
    dds.scope.enter_context(
        dds.set_contents(
            'src/1.cpp',
            b'''
            int value_1() { return 33; }
            ''',
        ))
    # 33 - 32 = 1
    assert build_and_get_rc(dds) == 1


def test_rebuild_header_change(dds: DDS):
    """Change the content of the header which defines the values"""
    assert build_and_get_rc(dds) == 0
    dds.scope.enter_context(
        dds.set_contents(
            'src/values.hpp',
            b'''
            const int first_value = 63;
            const int second_value = 88;
            ''',
        ))
    assert build_and_get_rc(dds) == (88 - 63)


def test_partial_build_rebuild(dds: DDS):
    """
    Change the content of a header, but cause one user of that header to fail
    compilation. The fact that compilation fails means it is still `out-of-date`,
    and will need to be compiled after we have fixed it up.
    """
    assert build_and_get_rc(dds) == 0
    dds.scope.enter_context(
        dds.set_contents(
            'src/values.hpp',
            b'''
            const int first_value_q  = 6;
            const int second_value_q = 99;
            ''',
        ))
    # Header now causes errors in 1.cpp and 2.cpp
    with pytest.raises(subprocess.CalledProcessError):
        dds.build()
    # Fix 1.cpp
    dds.scope.enter_context(
        dds.set_contents(
            'src/1.cpp',
            b'''
            #include "./values.hpp"

            int value_1() { return first_value_q; }
            ''',
        ))
    # We will still see a failure, but now the DB will record the updated values.hpp
    with pytest.raises(subprocess.CalledProcessError):
        dds.build()

    # Should should raise _again_, even though we've successfully compiled one
    # of the two files with the changed `values.hpp`, because `2.cpp` still
    # has a pending update
    with pytest.raises(subprocess.CalledProcessError):
        dds.build()

    dds.scope.enter_context(
        dds.set_contents(
            'src/2.cpp',
            b'''
            #include "./values.hpp"

            int value_2() { return second_value_q; }
            ''',
        ))
    # We should now compile and link to get the updated value
    assert build_and_get_rc(dds) == (99 - 6)