#!/usr/bin/env python3

# This differs from matrix-check in the following ways:
#   - it only tests one combination of features, as specified on the command line
#   - it doesn't have crate-specific hacks (or any knowledge of our workspace contents)
#     (but it does read some ad-hoc parseable comments from Cargo.toml's).
#   - it runs `cargo test`

import argparse
import subprocess
import sys
import list_crates


def test_crate(args, c):
    conditional_options = []

    for line in open(c.subdir + "/Cargo.toml"):

        # TODO do something more formal here
        #
        # We need this because some crates don't compile without a runtime selected.
        #
        # Ideally, if the crate doesn't compile without any features selected,
        # the manifest should have a `minimal` feature we can use, or something.
        if line.startswith("# @@ test-all-crates ignore"):
            print(
                """(
(((((((((( skipping %s ))))))))))
)"""
                % c.name,
                file=sys.stderr,
            )
            return

        conditional_option_directive = "# @@ test-all-crates conditional-option "
        # One option per line, so it can contain spaces
        if line.startswith(conditional_option_directive):
            (key, option) = (
                line[len(conditional_option_directive) :].rstrip().split(maxsplit=1)
            )
            if key in args.enable_conditional_options:
                conditional_options.append(option)
            continue

    command_sh = 'p=$1; shift; set -x; $CARGO test -p $p "$@"'

    print(
        """:
:::::::::: %s ::::::::::
:"""
        % c.name,
        file=sys.stderr,
    )

    # We run a separate build command for each one, to defeat cargo feature unification.

    command_l = (
        [
            "sh",
            "-ec",
            ': "${CARGO:=cargo --locked}"; ' + command_sh,
            "x",
            c.name,
        ]
        + args.cargo_option
        + conditional_options
    )

    child = subprocess.run(command_l)

    if child.returncode != 0:
        print(
            """failed command %s
"return code" %s
failed to test crate %s"""
            % (repr(command_l), child.returncode, c.name),
            file=sys.stderr,
        )
        sys.exit(1)


# https://stackoverflow.com/questions/24483182/python-split-list-into-n-chunks
def chunks(lst, n):
    """
    Yield n number of chunks from lst.

    This is a striped chunk, meaning that rather than making contiguous chunks, each chunk is made
    up of elements that are n spaces apart in the input list. This has the advantage of making
    sure that the lists are as close to evenly sized as possible, even if the input list length is
    not divisible by n.
    """
    for i in range(0, n):
        yield lst[i::n]


def main():
    parser = argparse.ArgumentParser(prog="test-all-crates")
    parser.add_argument("cargo_option", nargs="*")
    parser.add_argument("--enable-conditional-options", action="append", default=[])
    parser.add_argument("--skip-until", action="store", default=None)
    parser.add_argument(
        "--parallel",
        type=str,
        default="1,0",
        help="Split the checking into multiple chunks and run only a single one of the chunks. "
        "Takes two comma-separated integers, the first is the number of chunks, "
        "the second is which chunk to run (zero-indexed)."
        "This assumes that the set of crates that exist is the same between all invocations.",
    )
    args = parser.parse_args()

    parallel_chunks, parallel_n = map(int, args.parallel.split(","))
    assert parallel_n < parallel_chunks

    skip_until = args.skip_until

    crates = sorted(list_crates.list_crates(), key=lambda crate: crate.name)

    for crate in list(chunks(crates, parallel_chunks))[parallel_n]:
        if skip_until is not None:
            if skip_until == crate.name:
                skip_until = None
            else:
                print(
                    "skipping due to --skip-until %s: %s" % (skip_until, crate.name),
                    file=sys.stderr,
                )
                continue

        test_crate(args, crate)


main()
