# Copyright (c) 2016-2021 <NAME>
# Licensed under the zlib/libpng License
# https://opensource.org/licenses/Zlib

# libusb test library helper functions
# Copyright © 2012 <NAME> <<EMAIL>>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import sys
import os


class test_result:
    """Values returned from a test function to indicate test result"""
    # Indicates that the test ran successfully.
    TEST_STATUS_SUCCESS = 0
    # Indicates that the test failed one or more test.
    TEST_STATUS_FAILURE = 1
    # Indicates that an unexpected error occurred.
    TEST_STATUS_ERROR = 2
    # Indicates that the test can't be run. For example this may be
    # due to no suitable device being connected to perform the tests.
    TEST_STATUS_SKIP = 3


class test_spec:
    """Structure holding a test description."""

    def __init__(self, name, function):
        # Human readable name of the test.
        self.name = name
        # The test library will call this function to run the test.
        # Should return test_result.TEST_STATUS_SUCCESS on success or
        # another test_result.TEST_STATUS_* value.
        self.function = function


def test_result_to_str(result: test_result) -> str:
    """Converts a test result code into a human readable string."""
    test_result_str = {
        test_result.TEST_STATUS_SUCCESS: "Success",
        test_result.TEST_STATUS_FAILURE: "Failure",
        test_result.TEST_STATUS_ERROR:   "Error",
        test_result.TEST_STATUS_SKIP:    "Skip",
    }

    return test_result_str.get(result, "Unknown")


def print_usage(progname: str):
    print("Usage: {} [-l] [-v] [<test_name> ...]".format(progname))
    print("   -l   List available tests")
    print("   -v   Don't redirect STDERR before running tests")
    print("   -h   Display this help and exit")


def logf(fmt: str, *args):
    """Logs some test information or state"""
    print(fmt.format(*args), file=sys.stdout)
    sys.stdout.flush()


def run_tests(argv: list, tests: list) -> int:
    """Runs the tests provided.

    Before running any tests argv will be processed
    to determine the mode of operation.

    \param argv The argv from main
    \param tests A NULL_TEST terminated array of tests
    \return 0 on success, non-zero on failure"""

    # Setup default mode of operation
    test_names = None
    list_tests = False
    verbose = False

    # Parse command line options
    for j, arg in enumerate(argv):
        if arg[0] in ('-', '/'):
            if len(arg) == 2:
                opt = arg[1]
                if opt == 'l':
                    list_tests = True
                    continue
                elif opt == 'v':
                    verbose = True
                    continue
                elif opt == 'h':
                    print_usage(sys.argv[0])
                    return 0

            print("Unknown option: '{}'".format(arg), file=sys.stderr)
            print_usage(sys.argv[0])
            return 1
        else:
            # End of command line options, remaining must be list
            # of tests to run
            test_names = argv[j:]
            break

    # Validate command line options
    if test_names and list_tests:
        print("List of tests requested but test list provided", file=sys.stderr)
        print_usage(sys.argv[0])
        return 1

    # Setup test log output
    if not verbose:
        try:
            sys.stderr.close()
        except: pass
        try:
            sys.stderr = open(os.devnull, "w")
        except Exception as exc:
            print("Failed to open null handle: {:d}".format(exc.errno))
            return 1

    # Act on any options not related to running tests

    if list_tests:
        for test in tests:
            logf("{}", test.name)
        return 0

    # Run any requested tests

    run_count = 0
    pass_count = 0
    fail_count = 0
    error_count = 0
    skip_count = 0

    for test in tests:

        if test_names:
            # Filtering tests to run, check if this is one of them
            for test_name in test_names:
                if test_name == test.name:
                    # Matches a requested test name
                    break
            else:
                # Failed to find a test match, so do the next loop iteration
                continue

        logf("Starting test run: {}...", test.name)
        result = test.function()
        logf("{} ({:d})", test_result_to_str(result), result)
        if   result == test_result.TEST_STATUS_SUCCESS: pass_count += 1
        elif result == test_result.TEST_STATUS_FAILURE: fail_count += 1
        elif result == test_result.TEST_STATUS_ERROR: error_count += 1
        elif result == test_result.TEST_STATUS_SKIP: skip_count += 1
        run_count += 1

    logf("---")
    logf("Ran {:d} tests", run_count)
    logf("Passed {:d} tests", pass_count)
    logf("Failed {:d} tests", fail_count)
    logf("Error in {:d} tests", error_count)
    logf("Skipped {:d} tests", skip_count)

    return int(pass_count != run_count)

# Copyright (c) 2016-2021 <NAME>
# Licensed under the zlib/libpng License
# https://opensource.org/licenses/Zlib

# libusb test library helper functions
# Copyright © 2012 <NAME> <<EMAIL>>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import sys
import os


class test_result:
    """Values returned from a test function to indicate test result"""
    # Indicates that the test ran successfully.
    TEST_STATUS_SUCCESS = 0
    # Indicates that the test failed one or more test.
    TEST_STATUS_FAILURE = 1
    # Indicates that an unexpected error occurred.
    TEST_STATUS_ERROR = 2
    # Indicates that the test can't be run. For example this may be
    # due to no suitable device being connected to perform the tests.
    TEST_STATUS_SKIP = 3


class test_spec:
    """Structure holding a test description."""

    def __init__(self, name, function):
        # Human readable name of the test.
        self.name = name
        # The test library will call this function to run the test.
        # Should return test_result.TEST_STATUS_SUCCESS on success or
        # another test_result.TEST_STATUS_* value.
        self.function = function


def test_result_to_str(result: test_result) -> str:
    """Converts a test result code into a human readable string."""
    test_result_str = {
        test_result.TEST_STATUS_SUCCESS: "Success",
        test_result.TEST_STATUS_FAILURE: "Failure",
        test_result.TEST_STATUS_ERROR:   "Error",
        test_result.TEST_STATUS_SKIP:    "Skip",
    }

    return test_result_str.get(result, "Unknown")


def print_usage(progname: str):
    print("Usage: {} [-l] [-v] [<test_name> ...]".format(progname))
    print("   -l   List available tests")
    print("   -v   Don't redirect STDERR before running tests")
    print("   -h   Display this help and exit")


def logf(fmt: str, *args):
    """Logs some test information or state"""
    print(fmt.format(*args), file=sys.stdout)
    sys.stdout.flush()


def run_tests(argv: list, tests: list) -> int:
    """Runs the tests provided.

    Before running any tests argv will be processed
    to determine the mode of operation.

    \param argv The argv from main
    \param tests A NULL_TEST terminated array of tests
    \return 0 on success, non-zero on failure"""

    # Setup default mode of operation
    test_names = None
    list_tests = False
    verbose = False

    # Parse command line options
    for j, arg in enumerate(argv):
        if arg[0] in ('-', '/'):
            if len(arg) == 2:
                opt = arg[1]
                if opt == 'l':
                    list_tests = True
                    continue
                elif opt == 'v':
                    verbose = True
                    continue
                elif opt == 'h':
                    print_usage(sys.argv[0])
                    return 0

            print("Unknown option: '{}'".format(arg), file=sys.stderr)
            print_usage(sys.argv[0])
            return 1
        else:
            # End of command line options, remaining must be list
            # of tests to run
            test_names = argv[j:]
            break

    # Validate command line options
    if test_names and list_tests:
        print("List of tests requested but test list provided", file=sys.stderr)
        print_usage(sys.argv[0])
        return 1

    # Setup test log output
    if not verbose:
        try:
            sys.stderr.close()
        except: pass
        try:
            sys.stderr = open(os.devnull, "w")
        except Exception as exc:
            print("Failed to open null handle: {:d}".format(exc.errno))
            return 1

    # Act on any options not related to running tests

    if list_tests:
        for test in tests:
            logf("{}", test.name)
        return 0

    # Run any requested tests

    run_count = 0
    pass_count = 0
    fail_count = 0
    error_count = 0
    skip_count = 0

    for test in tests:

        if test_names:
            # Filtering tests to run, check if this is one of them
            for test_name in test_names:
                if test_name == test.name:
                    # Matches a requested test name
                    break
            else:
                # Failed to find a test match, so do the next loop iteration
                continue

        logf("Starting test run: {}...", test.name)
        result = test.function()
        logf("{} ({:d})", test_result_to_str(result), result)
        if   result == test_result.TEST_STATUS_SUCCESS: pass_count += 1
        elif result == test_result.TEST_STATUS_FAILURE: fail_count += 1
        elif result == test_result.TEST_STATUS_ERROR: error_count += 1
        elif result == test_result.TEST_STATUS_SKIP: skip_count += 1
        run_count += 1

    logf("---")
    logf("Ran {:d} tests", run_count)
    logf("Passed {:d} tests", pass_count)
    logf("Failed {:d} tests", fail_count)
    logf("Error in {:d} tests", error_count)
    logf("Skipped {:d} tests", skip_count)

    return int(pass_count != run_count)

# Copyright (c) 2016-2021 <NAME> # Licensed under the zlib/libpng License # https://opensource.org/licenses/Zlib # libusb test library helper functions # Copyright © 2012 <NAME> <<EMAIL>> # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Values returned from a test function to indicate test result # Indicates that the test ran successfully. # Indicates that the test failed one or more test. # Indicates that an unexpected error occurred. # Indicates that the test can't be run. For example this may be # due to no suitable device being connected to perform the tests. Structure holding a test description. # Human readable name of the test. # The test library will call this function to run the test. # Should return test_result.TEST_STATUS_SUCCESS on success or # another test_result.TEST_STATUS_* value. Converts a test result code into a human readable string. Logs some test information or state Runs the tests provided.      Before running any tests argv will be processed     to determine the mode of operation.      \param argv The argv from main     \param tests A NULL_TEST terminated array of tests     \return 0 on success, non-zero on failure # Setup default mode of operation # Parse command line options # End of command line options, remaining must be list # of tests to run # Validate command line options # Setup test log output # Act on any options not related to running tests # Run any requested tests # Filtering tests to run, check if this is one of them # Matches a requested test name # Failed to find a test match, so do the next loop iteration