#!/usr/bin/env python
#     Copyright 2013, 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-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-wine",
    action  = "store_true",
    dest    = "no_wine",
    default = False,
    help    = """\
Do not use wine to cross compile. 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 == "wine" and options.no_wine:
        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

    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 ):
    parts = command.split()

    parts[0] = parts[0].replace( "/", os.path.sep )

    if parts[0].endswith( ".py" ) and os.name == "nt":
        parts.insert( 0, r"C:\Python27\python.exe" )

    command = " ".join( parts )

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

    sys.stdout.flush()
    result = subprocess.call( command, shell = True )

    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":
            os.environ[ "PYTHON" ] = r"C:\Python26\python.exe"
        elif use_python == "python2.7":
            os.environ[ "PYTHON" ] = r"C:\Python27\python.exe"
        elif use_python == "python3.2":
            os.environ[ "PYTHON" ] = r"C:\Python32\python.exe"
        elif use_python == "python3.3":
            os.environ[ "PYTHON" ] = r"C:\Python33\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.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" ) and \
       "--windows-target" not in flags:
        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 "--windows-target" not in flags and "--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" )

# Just the quick syntax test, full tests are run later.
if checkExecutableCommand( "python3.2" ):
    executeSubTest( "python3.2 bin/nuitka --version 2>/dev/null" )

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 "linux" in sys.platform and checkExecutableCommand( "wine" ) :
    execute_tests( "python-wine", "python", "--windows-target" )
else:
    print( "Cannot execute wine cross-compile tests, disabled or no wine." )

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, shell = True )

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

print( "OK." )
