#!/usr/bin/env python
#     Copyright 2014, Kay Hayen, mailto:kay.hayen@gmail.com
#
#     Part of "Nuitka", an optimizing Python compiler that is compatible and
#     integrates with CPython, but also works on its own.
#
#     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.
#

from __future__ import print_function

import os, sys, tempfile, subprocess

if "nocheck" in os.environ.get( "DEB_BUILD_OPTIONS", "" ).split():
    print( "Skiped all tests as per DEB_BUILD_OPTIONS environment." )
    sys.exit( 0 )

from optparse import OptionParser

parser = OptionParser()

parser.add_option(
    "--skip-basic-tests",
    action  = "store_false",
    dest    = "basic_tests",
    default = True,
    help    = """\
The basic tests, execute these to check if Nuitka is healthy. Default
is %default."""
)

parser.add_option(
    "--skip-syntax-tests",
    action  = "store_false",
    dest    = "syntax_tests",
    default = True,
    help    = """\
The syntax tests, execute these to check if Nuitka handles Syntax errors fine.
Default is %default."""
)

parser.add_option(
    "--skip-program-tests",
    action  = "store_false",
    dest    = "program_tests",
    default = True,
    help    = """\
The programs tests, execute these to check if Nuitka handles programs, e.g.
import recursions, etc. fine. Default is %default."""
)

parser.add_option(
    "--skip-package-tests",
    action  = "store_false",
    dest    = "package_tests",
    default = True,
    help    = """\
The packages tests, execute these to check if Nuitka handles packages, e.g.
import recursions, etc. fine. Default is %default."""
)

parser.add_option(
    "--skip-optimizations-tests",
    action  = "store_false",
    dest    = "optimization_tests",
    default = True,
    help    = """\
The optimization tests, execute these to check if Nuitka does optimize certain
constructs fully away. Default is %default."""
)

parser.add_option(
    "--skip-standalone-tests",
    action  = "store_false",
    dest    = "standalone_tests",
    default = os.name != "posix" or os.uname()[0] != "NetBSD",
    help    = """\
The standalone tests, execute these to check if Nuitka standalone mode, e.g.
not referring to outside, important 3rd library packages like PyQt fine.
Default is %default."""
)

parser.add_option(
    "--skip-reflection-test",
    action  = "store_false",
    dest    = "reflection_test",
    default = True,
    help    = """\
The reflection test compiles Nuitka with Nuitka, and then Nuitka with the
compile Nuitka and compares the outputs. Default is %default."""
)

parser.add_option(
    "--skip-cpython26-tests",
    action  = "store_false",
    dest    = "cpython26",
    default = True,
    help    = """\
The standard CPython2.6 test suite. Execute this for all corner cases to be
covered. With Python 2.7 this covers exception behavior quite well. Default
is %default."""
)

parser.add_option(
    "--skip-cpython27-tests",
    action  = "store_false",
    dest    = "cpython27",
    default = True,
    help    = """\
The standard CPython2.7 test suite. Execute this for all corner cases to be
covered. With Python 2.6 these are not run. Default is %default."""
)

parser.add_option(
    "--skip-cpython32-tests",
    action  = "store_false",
    dest    = "cpython32",
    default = True,
    help    = """\
The standard CPython3.2 test suite. Execute this for all corner cases to be
covered. With Python 2.6 these are not run. Default is %default."""
)

parser.add_option(
    "--skip-cpython33-tests",
    action  = "store_false",
    dest    = "cpython33",
    default = True,
    help    = """\
The standard CPython3.3 test suite. Execute this for all corner cases to be
covered. With Python 2.x these are not run. Default is %default."""
)

parser.add_option(
    "--no-python2.6",
    action  = "store_true",
    dest    = "no26",
    default = False,
    help    = """\
Do not use Python2.6 even if available on the system. Default is %default."""
)


parser.add_option(
    "--no-python2.7",
    action  = "store_true",
    dest    = "no27",
    default = False,
    help    = """\
Do not use Python2.7 even if available on the system. Default is %default."""
)

parser.add_option(
    "--no-python3.2",
    action  = "store_true",
    dest    = "no32",
    default = False,
    help    = """\
Do not use Python3.2 even if available on the system. Default is %default."""
)

parser.add_option(
    "--no-python3.3",
    action  = "store_true",
    dest    = "no33",
    default = False,
    help    = """\
Do not use Python3.3 even if available on the system. Default is %default."""
)

parser.add_option(
    "--no-python3.4",
    action  = "store_true",
    dest    = "no34",
    default = False,
    help    = """\
Do not use Python3.4 even if available on the system. Default is %default."""
)

options, positional_args = parser.parse_args()

if positional_args:
    parser.print_help()

    sys.exit( "\nError, no positional argument allowed." )

# Go its own directory, to have it easy with path knowledge.
os.chdir( os.path.dirname( os.path.abspath( __file__ ) ) )
os.chdir( ".." )

path_sep = ";" if os.name == "nt" else ":"

# Add the local bin directory to search path start.
os.environ[ "PATH" ] = \
  os.path.join(
    os.getcwd(),
    "bin" ) + \
  path_sep + \
  os.environ[ "PATH" ]

def checkExecutableCommand( command ):
    """ Check if a command is executable. """

    # Do respect given options to disable specific Python versions
    if command == "python2.6" and options.no26:
        return False
    if command == "python2.7" and options.no27:
        return False
    if command == "python3.2" and options.no32:
        return False
    if command == "python3.3" and options.no33:
        return False
    if command == "python3.4" and options.no34:
        return False

    # Shortcuts for python versions, also needed for Windows as it won't have
    # the version number in the Python binaries at all.
    if command == "python2.6" and sys.version_info[0:2] == (2,6):
       return True
    if command == "python2.7" and sys.version_info[0:2] == (2,7):
       return True
    if command == "python3.2" and sys.version_info[0:2] == (3,2):
       return True
    if command == "python3.3" and sys.version_info[0:2] == (3,3):
       return True
    if command == "python3.4" and sys.version_info[0:2] == (3,4):
       return True

    path = os.environ[ "PATH" ]

    suffixes = ( ".exe", ) if os.name == "nt" else ( "", )

    for part in path.split( path_sep ):
        if not part:
            continue

        for suffix in suffixes:
            if os.path.exists( os.path.join( part, command + suffix ) ):
                return True
    else:
        return False

def setExtraFlags( where, name, flags ):
    if where is not None:
        tmp_dir = tempfile.gettempdir()

        # Try to avoid RAM disk /tmp and use the disk one instead.
        if tmp_dir == "/tmp" and os.path.exists( "/var/tmp" ):
            tmp_dir = "/var/tmp"

        where = os.path.join( tmp_dir, name, where )

        if not os.path.exists( where ):
            os.makedirs( where )

        os.environ[ "NUITKA_EXTRA_OPTIONS" ] = flags + " --output-dir=" + where
    else:
        os.environ[ "NUITKA_EXTRA_OPTIONS" ] = flags

def executeSubTest( command, hide_stderror = False ):
    parts = command.split()
    parts[0] = parts[0].replace( "/", os.path.sep )

    # On Windows, we need to specify Python ourselves.
    if os.name == "nt":
        parts.insert( 0, sys.executable )

    print( "Run '%s' in '%s'." % ( " ".join( parts ), os.getcwd() ) )

    sys.stdout.flush()
    result = subprocess.call( parts )

    if result != 0:
        sys.exit( result )

def execute_tests( where, use_python, flags ):
    print(
        "Executing test case called %s with CPython %s and extra flags '%s'." % (
            where,
            use_python,
            flags
        )
    )

    if os.name == "nt":
        if use_python == "python2.6":
            if sys.version.startswith( "2.6"):
                os.environ[ "PYTHON" ] = sys.executable
            else:
                os.environ[ "PYTHON" ] = r"C:\Python26\python.exe"
        elif use_python == "python2.7":
            if sys.version.startswith( "2.7"):
                os.environ[ "PYTHON" ] = sys.executable
            else:
                os.environ[ "PYTHON" ] = r"C:\Python27\python.exe"
        elif use_python == "python3.2":
            if sys.version.startswith( "3.2"):
                os.environ[ "PYTHON" ] = sys.executable
            else:
                os.environ[ "PYTHON" ] = r"C:\Python32\python.exe"
        elif use_python == "python3.3":
            if sys.version.startswith( "3.3"):
                os.environ[ "PYTHON" ] = sys.executable
            else:
                os.environ[ "PYTHON" ] = r"C:\Python33\python.exe"
        elif use_python == "python3.4":
            if sys.version.startswith( "3.4"):
                os.environ[ "PYTHON" ] = sys.executable
            else:
                os.environ[ "PYTHON" ] = r"C:\Python34\python.exe"
        else:
            assert False, use_python
    else:
        os.environ[ "PYTHON" ] = use_python

    if options.basic_tests:
        print( "Running the basic tests with options '%s' with %s:"  % ( flags, use_python ) )
        setExtraFlags( where, "basics", flags )
        executeSubTest( "./tests/basics/run_all.py search" )

    if options.syntax_tests:
        print( "Running the syntax tests with options '%s' with %s:"  % ( flags, use_python ) )
        setExtraFlags( where, "syntax", flags )
        executeSubTest( "./tests/syntax/run_all.py search" )

    if options.program_tests:
        print( "Running the program tests with options '%s' with %s:" % ( flags, use_python ) )
        setExtraFlags( where, "programs", flags )
        executeSubTest( "./tests/programs/run_all.py search" )

    if options.package_tests:
        print( "Running the package tests with options '%s' with %s:" % ( flags, use_python ) )
        setExtraFlags( where, "packages", flags )
        executeSubTest( "./tests/packages/run_all.py search" )

    # At least one Debian Jessie, these versions won't have lxml installed, so
    # don't run them there. Also these won't be very version dependent in their
    # results.
    if use_python != "python2.6" and use_python != "python3.2":
        if options.optimization_tests:
            print( "Running the optimizations tests with options '%s' with %s:" % ( flags, use_python ) )
            setExtraFlags( where, "optimizations", flags )
            executeSubTest( "./tests/optimizations/run_all.py search" )

    if options.standalone_tests:
        print( "Running the standalone tests with options '%s' with %s:" % ( flags, use_python ) )
        setExtraFlags( None, "standalone", flags )
        executeSubTest( "./tests/standalone/run_all.py search" )

    if options.reflection_test:
        print( "Running the reflection test with options '%s' with %s:" % ( flags, use_python ) )
        setExtraFlags( None, "reflected", flags )
        executeSubTest( "./tests/reflected/compile_itself.py search" )

    if not use_python.startswith( "python3" ):
        if os.path.exists( "./tests/CPython26/run_all.py" ):
            if options.cpython26:
                print( "Running the CPython 2.6 tests with options '%s' with %s:" % ( flags, use_python ) )

                setExtraFlags( where, "26tests", flags )
                executeSubTest( "./tests/CPython26/run_all.py search" )
        else:
            print( "The CPython2.6 tests are not present, not run." )

        # Running the Python 2.7 test suite with CPython 2.6 gives little
        # insight, because "importlib" will not be there and that's it.
        if use_python != "python2.6":
            if os.path.exists( "./tests/CPython27/run_all.py" ):
                if options.cpython27:
                    print( "Running the CPython 2.7 tests with options '%s' with %s:" % ( flags, use_python ) )
                    setExtraFlags( where, "27tests", flags )
                    executeSubTest( "./tests/CPython27/run_all.py search" )
            else:
                print( "The CPython2.7 tests are not present, not run." )

    if "--debug" not in flags:
        # Not running the Python 3.2 test suite with CPython2.6, as that's about
        # the same as CPython2.7 and won't have any new insights.
        if use_python != "python2.6":
            if os.path.exists( "./tests/CPython32/run_all.py" ):
                if options.cpython32:
                    setExtraFlags( where, "32tests", flags )
                    executeSubTest( "./tests/CPython32/run_all.py search" )
            else:
                print( "The CPython3.2 tests are not present, not run." )

        # Running the Python 3.3 test suite only with CPython2.x.
        if not use_python.startswith( "python2" ):
            if os.path.exists( "./tests/CPython33/run_all.py" ):
                if options.cpython33:
                    setExtraFlags( where, "33tests", flags )
                    executeSubTest( "./tests/CPython33/run_all.py search" )
            else:
                print( "The CPython3.3 tests are not present, not run." )

    if "NUITKA_EXTRA_OPTIONS" in os.environ:
        del os.environ[ "NUITKA_EXTRA_OPTIONS" ]

assert checkExecutableCommand( "python2.6" ) or checkExecutableCommand( "python2.7" ) or checkExecutableCommand( "python3.2" ) or checkExecutableCommand( "python3.3" ) or checkExecutableCommand( "python3.4" )

# Just the quick syntax test, full tests are run later.
if checkExecutableCommand( "python3.2" ):
    executeSubTest(
        "bin/nuitka --python-version=3.2 --version",
        hide_stderror = True
    )

if checkExecutableCommand( "python2.6" ):
    execute_tests( "python2.6-debug", "python2.6", "--debug" )
else:
    print( "Cannot execute tests with Python 2.6, disabled or not installed." )

if checkExecutableCommand( "python2.7" ):
    execute_tests( "python2.7-debug", "python2.7", "--debug" )
else:
    print( "Cannot execute tests with Python 2.7, disabled or not installed." )

# Temporary measure, the nodebug tests pass, debug doesn't for the CPython3.2
# test suite without unused closure variable removal.
if False and checkExecutableCommand( "python3.2" ):
    execute_tests( "python3.2-debug", "python3.2", "--debug" )
else:
    print( "Cannot execute tests with Python 3.2, disabled or not installed." )

if checkExecutableCommand( "python2.6" ):
    execute_tests( "python2.6-nodebug", "python2.6", "" )
else:
    print( "Cannot execute tests with Python 2.6, disabled or not installed." )

if checkExecutableCommand( "python2.7" ):
    execute_tests( "python2.7-nodebug", "python2.7", "" )
else:
    print( "Cannot execute tests with Python 2.7, disabled or not installed." )

if checkExecutableCommand( "python3.2" ):
    execute_tests( "python3.2-nodebug", "python3.2", "" )
else:
    print( "Cannot execute tests with Python 3.2, disabled or not installed." )

if checkExecutableCommand( "python3.3" ):
    execute_tests( "python3.2-nodebug", "python3.3", "" )
else:
    print( "Cannot execute tests with Python 3.3, disabled or not installed." )

if checkExecutableCommand( "python3.4" ):
    execute_tests( "python3.2-nodebug", "python3.4", "" )
else:
    print( "Cannot execute tests with Python 3.4, disabled or not installed." )

if checkExecutableCommand( "cppcheck" ):
    command = [
        "cppcheck",
        "-q",
        "--error-exitcode=1",
        "--enable=all",
        "--check-config", "nuitka/build/",
        "-I", "nuitka/build/include/",
        "-I", "/usr/include/python2.7"
    ]

    sys.stdout.flush()
    result = subprocess.call( command )

    if result != 0:
        sys.exit( result )

print( "OK." )
