"""
This module contains rules and macros for building and testing Go code.
"""

load("@io_bazel_rules_go//go:def.bzl", _go_test = "go_test")

def go_test(ld = None, count = 3, **kwargs):
    """go_test is a wrapper for go_test that uses default settings for Constellation.

    It adds the following:
    - Sets test count to 3.
    - Sets race detector to on by default.
    - Optionally sets the interpreter path to ld.

    Args:
          ld: path to interpreter to that will be written into the elf header.
          count: number of times each test should be executed. defaults to 3.
          **kwargs: all other arguments are passed to go_test.
    """

    # Sets test count to 3.
    kwargs.setdefault("args", [])
    kwargs["args"].append("--test.count={}".format(count))

    # enable race detector by default
    kwargs.setdefault("race", "on")

    # set gc_linkopts to set the interpreter path to ld.
    kwargs.setdefault("gc_linkopts", [])
    if ld:
        kwargs["gc_linkopts"] += ["-I", ld]

    _go_test(**kwargs)

def _vars_script(env, ld, cmd):
    ret = ["#!/bin/sh"]
    for k, v in env.items():
        ret.append('export {}="{}"'.format(k, v))
    ret.append('exec {} {} "$@"'.format(ld, cmd))
    return "\n".join(ret) + "\n"

def _ld_binary_impl(ctx):
    source_info = ctx.attr.src[DefaultInfo]
    ld_info = ctx.attr.ld[DefaultInfo]

    executable = None
    if source_info.files_to_run and source_info.files_to_run.executable:
        command = _vars_script(ctx.attr.env, ld_info.files_to_run.executable.short_path, source_info.files_to_run.executable.short_path)
        executable = ctx.actions.declare_file("{}_under_ld".format(ctx.file.src.basename))
        ctx.actions.write(
            output = executable,
            content = command,
            is_executable = True,
        )

    runfiles = ctx.runfiles(files = ctx.files.src)
    runfiles = runfiles.merge(source_info.data_runfiles)
    runfiles = runfiles.merge(ctx.runfiles(files = ctx.files.ld))
    runfiles = runfiles.merge(ld_info.data_runfiles)
    return [DefaultInfo(
        executable = executable,
        files = depset([executable]),
        runfiles = runfiles,
    )]

_attrs = {
    "env": attr.string_dict(
        doc = "Environment variables for the test",
    ),
    "ld": attr.label(
        executable = True,
        cfg = "exec",
        doc = "ld wrapper executable",
    ),
    "src": attr.label(
        allow_single_file = True,
        mandatory = True,
        doc = "Target to build.",
    ),
}

ld_test = rule(
    implementation = _ld_binary_impl,
    attrs = _attrs,
    executable = True,
    test = True,
)

def go_ld_test(**kwargs):
    """go_ld_test is a wrapper for go_test that uses the specified ld to run the test binary under.

    Args:
          **kwargs: all arguments are passed to go_test.
    """

    # Sets test count to 3.
    kwargs.setdefault("args", [])
    kwargs["args"].append("--test.count=3")

    # Disable test wrapper
    kwargs.setdefault("env", {})
    kwargs["env"]["GO_TEST_WRAP"] = "0"

    ld_test(
        **kwargs
    )