#!/usr/bin/env python3

# Copyright (C) 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import pathlib
import subprocess
import sys

SOURCE_ENVSETUP="source build/make/envsetup.sh && "

def update_display():
    sys.stderr.write("passed\n")

def go_to_root():
    while True:
        if os.path.exists("build/make/envsetup.sh"):
            return
        if os.getcwd() == "/":
            sys.stderr.write("Can't find root of the source tree\n");
            print("\nFAILED")
            sys.exit(1)
        os.chdir("..")

def is_test(name, thing):
    if not callable(thing):
        return False
    if name == "test":
        return False
    return name.startswith("test")


def test(shell, command, expected_return, expected_stdout, expected_stderr, expected_env):
    command += "; _rc=$?"
    for env in expected_env.keys():
        command += f"; echo ENV: {env}=\\\"${env}\\\""
    command += "; exit $_rc"

    cmd = [shell, "-c", command]
    result = subprocess.run(cmd, capture_output=True, text=True)

    status = True

    if result.returncode != expected_return:
        print()
        print(f"Expected return code: {expected_return}")
        print(f"Actual return code:   {result.returncode}")
        status = False

    printed_stdout = False
    if expected_stdout and expected_stdout not in result.stdout:
        print()
        print(f"Expected stdout to contain:\n{expected_stdout}")
        print(f"\nActual stdout:\n{result.stdout}")
        printed_stdout = True
        status = False

    if expected_stderr and expected_stderr not in result.stderr:
        print()
        print(f"Expected stderr to contain:\n{expected_stderr}")
        print(f"\nActual stderr:\n{result.stderr}")
        status = False

    env_failure = False
    for k, v in expected_env.items():
        if f"{k}=\"{v}\"" not in result.stdout:
            print()
            print(f"Expected environment variable {k} to be: {v} --- {k}=\"{v}\"")
            env_failure = True
            status = False

    if env_failure and not printed_stdout:
        print()
        print("See stdout:")
        print(result.stdout)

    if not status:
        print()
        print("Command to reproduce:")
        print(command)
        print()

    return status

NO_LUNCH = {
    "TARGET_PRODUCT": "",
    "TARGET_RELEASE": "",
    "TARGET_BUILD_VARIANT": "",
}

def test_invalid_lunch_target(shell):
    return test(shell, SOURCE_ENVSETUP + "lunch invalid-trunk_staging-eng",
         expected_return=1, expected_stdout=None,
         expected_stderr="Cannot locate config makefile for product",
         expected_env=NO_LUNCH)


def test_aosp_arm(shell):
    return test(shell, SOURCE_ENVSETUP + "lunch aosp_arm-trunk_staging-eng",
         expected_return=0, expected_stdout=None, expected_stderr=None,
         expected_env={
            "TARGET_PRODUCT": "aosp_arm",
            "TARGET_RELEASE": "trunk_staging",
            "TARGET_BUILD_VARIANT": "eng",
        })


def test_lunch2_empty(shell):
    return test(shell, SOURCE_ENVSETUP + "lunch2",
         expected_return=1, expected_stdout=None,
         expected_stderr="No target specified. See lunch --help",
         expected_env=NO_LUNCH)

def test_lunch2_four_params(shell):
    return test(shell, SOURCE_ENVSETUP + "lunch2 a b c d",
         expected_return=1, expected_stdout=None,
         expected_stderr="Too many parameters given. See lunch --help",
         expected_env=NO_LUNCH)

def test_lunch2_aosp_arm(shell):
    return test(shell, SOURCE_ENVSETUP + "lunch2 aosp_arm",
         expected_return=0, expected_stdout="=========", expected_stderr=None,
         expected_env={
            "TARGET_PRODUCT": "aosp_arm",
            "TARGET_RELEASE": "trunk_staging",
            "TARGET_BUILD_VARIANT": "eng",
        })

def test_lunch2_aosp_arm_trunk_staging(shell):
    # Somewhat unfortunate because trunk_staging is the only config in
    # aosp so we can't really test that this isn't just getting the default
    return test(shell, SOURCE_ENVSETUP + "lunch2 aosp_arm trunk_staging",
         expected_return=0, expected_stdout="=========", expected_stderr=None,
         expected_env={
            "TARGET_PRODUCT": "aosp_arm",
            "TARGET_RELEASE": "trunk_staging",
            "TARGET_BUILD_VARIANT": "eng",
        })

def test_lunch2_aosp_arm_trunk_staging_userdebug(shell):
    return test(shell, SOURCE_ENVSETUP + "lunch2 aosp_arm trunk_staging userdebug",
         expected_return=0, expected_stdout="=========", expected_stderr=None,
         expected_env={
            "TARGET_PRODUCT": "aosp_arm",
            "TARGET_RELEASE": "trunk_staging",
            "TARGET_BUILD_VARIANT": "userdebug",
        })

def test_list_products(shell):
    return test(shell, "build/soong/bin/list_products",
         expected_return=0, expected_stdout="aosp_arm", expected_stderr=None,
         expected_env=NO_LUNCH)

def test_list_releases_param(shell):
    return test(shell, "build/soong/bin/list_releases aosp_arm",
         expected_return=0, expected_stdout="trunk_staging", expected_stderr=None,
         expected_env=NO_LUNCH)

def test_list_releases_env(shell):
    return test(shell, "TARGET_PRODUCT=aosp_arm build/soong/bin/list_releases",
         expected_return=0, expected_stdout="trunk_staging", expected_stderr=None,
         expected_env=NO_LUNCH)

def test_list_releases_no_product(shell):
    return test(shell, "build/soong/bin/list_releases",
         expected_return=1, expected_stdout=None, expected_stderr=None,
         expected_env=NO_LUNCH)

def test_list_variants(shell):
    return test(shell, "build/soong/bin/list_variants",
         expected_return=0, expected_stdout="userdebug", expected_stderr=None,
         expected_env=NO_LUNCH)


def test_get_build_var_in_path(shell):
    return test(shell, SOURCE_ENVSETUP + "which get_build_var ",
         expected_return=0, expected_stdout="soong/bin", expected_stderr=None,
         expected_env=NO_LUNCH)



TESTS=sorted([(name, thing) for name, thing in locals().items() if is_test(name, thing)])

def main():
    if any([x.endswith("/soong/bin") for x in os.getenv("PATH").split(":")]):
        sys.stderr.write("run_envsetup_tests must be run in a shell that has not sourced"
                + " envsetup.sh\n\nFAILED\n")
        return 1

    go_to_root()

    tests = TESTS
    if len(sys.argv) > 1:
        tests = [(name, func) for name, func in tests if name in sys.argv]

    shells = ["/usr/bin/bash", "/usr/bin/zsh"]
    total_count = len(tests) * len(shells)
    index = 1
    failed_tests = 0

    for name, func in tests:
        for shell in shells:
            sys.stdout.write(f"\33[2K\r{index} of {total_count}: {name} in {shell}")
            passed = func(shell)
            if not passed:
                failed_tests += 1
            index += 1

    if failed_tests > 0:
        print(f"\n\nFAILED: {failed_tests} of {total_count}")
        return 1
    else:
        print("\n\nSUCCESS")
        return 0

if __name__ == "__main__":
    sys.exit(main())
