# ------------------------------------------------------------------------
# CxxTest: A lightweight C++ unit testing library.
# Copyright (c) 2008 Sandia Corporation.
# This software is distributed under the LGPL License v3
# For more information, see the COPYING file in the top CxxTest directory.
# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
# the U.S. Government retains certain rights in this software.
# ------------------------------------------------------------------------

import shutil
import time
import sys
import os
import os.path
import glob
import difflib
import subprocess
import re
if sys.version_info < (2, 7):
    import unittest2 as unittest
else:
    import unittest
try:
    import ply
    ply_available = True
except:
    ply_available = False
try:
    import cxxtest
    cxxtest_available = True
    import cxxtest.cxxtestgen
except:
    cxxtest_available = False

currdir = os.path.dirname(os.path.abspath(__file__)) + os.sep
sampledir = os.path.dirname(os.path.dirname(currdir)) + '/sample' + os.sep
cxxtestdir = os.path.dirname(os.path.dirname(currdir)) + os.sep

compilerre = re.compile("^(?P<path>[^:]+)(?P<rest>:.*)$")
dirre = re.compile("^([^%s]*/)*" % re.escape(os.sep))
xmlre = re.compile("\"(?P<path>[^\"]*/[^\"]*)\"")
datere = re.compile("date=\"[^\"]*\"")

# Headers from the cxxtest/sample directory
samples = ' '.join(file for file in sorted(glob.glob(sampledir + '*.h')))
guiInputs = currdir + '../sample/gui/GreenYellowRed.h'
if sys.platform.startswith('win'):
    target_suffix = '.exe'
    command_separator = ' && '
    cxxtestdir = '/'.join(cxxtestdir.split('\\'))
    remove_extra_path_prefixes_on_windows = True
else:
    target_suffix = ''
    command_separator = '; '
    remove_extra_path_prefixes_on_windows = False


def find(filename, executable=False, isfile=True, validate=None):
    #
    # Use the PATH environment if it is defined and not empty
    #
    if "PATH" in os.environ and os.environ["PATH"] != "":
        search_path = os.environ['PATH'].split(os.pathsep)
    else:
        search_path = os.defpath.split(os.pathsep)
    for path in search_path:
            test_fname = os.path.join(path, filename)
            if (
                    os.path.exists(test_fname)
                    and (not isfile or os.path.isfile(test_fname))
                    and (not executable or os.access(test_fname, os.X_OK))
            ):
                return os.path.abspath(test_fname)
    return None


def join_commands(command_one, command_two):
    return command_separator.join([command_one, command_two])

_available = {}


def available(compiler, exe_option):
    if (compiler, exe_option) in _available:
        return _available[compiler, exe_option]
    cmd = join_commands("cd %s" % currdir,
                        "%s %s %s %s > %s 2>&1" % (compiler, exe_option, currdir + 'anything', currdir + 'anything.cpp', currdir + 'anything.log'))
    print("Testing for compiler " + compiler)
    print("Command: " + cmd)
    status = subprocess.call(cmd, shell=True)
    executable = currdir + 'anything' + target_suffix
    flag = status == 0 and os.path.exists(executable)
    os.remove(currdir + 'anything.log')
    if os.path.exists(executable):
        os.remove(executable)
    print("Status: " + str(flag))
    _available[compiler, exe_option] = flag
    return flag


def remove_absdir(filename):
    INPUT = open(filename, 'r')
    lines = [line.strip() for line in INPUT]
    INPUT.close()
    OUTPUT = open(filename, 'w')
    for line in lines:
        # remove basedir at front of line
        match = compilerre.match(line)  # see if we can remove the basedir
        if match:
            parts = match.groupdict()
            line = dirre.sub("", parts['path']) + parts['rest']
        OUTPUT.write(line + '\n')
    OUTPUT.close()


def normalize_line_for_diff(line):
    # add spaces around {}<>()
    line = re.sub("[{}<>()]", r" \0 ", line)

    # beginnig and ending whitespace
    line = line.strip()

    # remove all whitespace
    # and leave a single space
    line = ' '.join(line.split())

    # remove spaces around "="
    line = re.sub(" ?= ?", "=", line)

    # remove all absolute path prefixes
    line = ''.join(line.split(cxxtestdir))

    if remove_extra_path_prefixes_on_windows:
        # Take care of inconsistent path prefixes like
        # "e:\path\to\cxxtest\test", "E:/path/to/cxxtest/test" etc
        # in output.
        line = ''.join(line.split(os.path.normcase(cxxtestdir)))
        line = ''.join(line.split(os.path.normpath(cxxtestdir)))
        # And some extra relative paths left behind
        line = re.sub(r'^.*[\\/]([^\\/]+\.(h|cpp))', r'\1', line)

    # for xml, remove prefixes from everything that looks like a
    # file path inside ""
    line = xmlre.sub(
        lambda match: '"' + re.sub("^[^/]+/", "", match.group(1)) + '"',
        line)
    # Remove date info
    line = datere.sub(lambda match: 'date=""', line)
    return line


def make_diff_readable(diff):
    i = 0
    while i + 1 < len(diff):
        if diff[i][0] == '-' and diff[i + 1][0] == '+':
            l1 = diff[i]
            l2 = diff[i + 1]
            for j in range(1, min([len(l1), len(l2)])):
                if l1[j] != l2[j]:
                    if j > 4:
                        j = j - 2
                        l1 = l1[j:]
                        l2 = l2[j:]
                        diff[i] = '-(...)' + l1
                        diff[i + 1] = '+(...)' + l2
                    break
        i += 1


def file_diff(filename1, filename2, filtered_reader):
    remove_absdir(filename1)
    remove_absdir(filename2)
    #
    INPUT = open(filename1, 'r')
    lines1 = list(filtered_reader(INPUT))
    INPUT.close()
    #
    INPUT = open(filename2, 'r')
    lines2 = list(filtered_reader(INPUT))
    INPUT.close()
    #
    diff = list(
        difflib.unified_diff(
            lines2, lines1,
            fromfile=filename2, tofile=filename1))
    if diff:
        make_diff_readable(diff)
        raise Exception("ERROR: \n\n%s\n\n%s\n\n%s\n\n" %
                        ('\n'.join(lines1),
                         '\n'.join(lines2),
                         '\n'.join(diff)))
    diff = '\n'.join(diff)
    return diff


class BaseTestCase(object):

    fog = ''
    valgrind = ''
    cxxtest_import = False

    def setUp(self):
        sys.stderr.write("(" + self.__class__.__name__ + ") ")
        self.passed = False
        self.prefix = ''
        self.py_out = ''
        self.py_cpp = ''
        self.px_pre = ''
        self.px_out = ''
        self.build_log = ''
        self.build_target = ''

    def tearDown(self):
        if not self.passed:
            return
        files = []
        if os.path.exists(self.py_out):
            files.append(self.py_out)
        if os.path.exists(self.py_cpp) and 'CXXTEST_GCOV_FLAGS' not in os.environ:
            files.append(self.py_cpp)
        if os.path.exists(self.px_pre):
            files.append(self.px_pre)
        if os.path.exists(self.px_out):
            files.append(self.px_out)
        if os.path.exists(self.build_log):
            files.append(self.build_log)
        if os.path.exists(self.build_target) and 'CXXTEST_GCOV_FLAGS' not in os.environ:
            files.append(self.build_target)
        for file in files:
            try:
                os.remove(file)
            except:
                time.sleep(2)
                try:
                    os.remove(file)
                except:
                    print("Error removing file '%s'" % file)

    # This is a "generator" that just reads a file and normalizes the lines
    def file_filter(self, file):
        for line in file:
            yield normalize_line_for_diff(line)

    def check_if_supported(self, filename, msg):
        target = currdir + 'check' + 'px' + target_suffix
        log = currdir + 'check' + '_build.log'
        cmd = join_commands("cd %s" % currdir,
                            "%s %s %s %s. %s%s../ %s > %s 2>&1" % (self.compiler, self.exe_option, target, self.include_option, self.include_option, currdir, filename, log))
        status = subprocess.call(cmd, shell=True)
        os.remove(log)
        if status != 0 or not os.path.exists(target):
            self.skipTest(msg)
        os.remove(target)

    def init(self, prefix):
        #
        self.prefix = self.__class__.__name__ + '_' + prefix
        self.py_out = currdir + self.prefix + '_py.out'
        self.py_cpp = currdir + self.prefix + '_py.cpp'
        self.px_pre = currdir + self.prefix + '_px.pre'
        self.px_out = currdir + self.prefix + '_px.out'
        self.build_log = currdir + self.prefix + '_build.log'
        self.build_target = currdir + self.prefix + 'px' + target_suffix

    def check_root(self, prefix='', output=None):
        self.init(prefix)
        args = "--have-eh --abort-on-fail --root --error-printer"
        if self.cxxtest_import:
            os.chdir(currdir)
            cxxtest.cxxtestgen.main(['cxxtestgen', self.fog, '-o', self.py_cpp] + re.split('[ ]+', args), True)
        else:
            cmd = join_commands(
                "cd %s" % currdir,
                "%s %s../bin/cxxtestgen %s -o %s %s > %s 2>&1" % (sys.executable, currdir, self.fog, self.py_cpp, args, self.py_out))
            status = subprocess.call(cmd, shell=True)
            self.assertEqual(status, 0, 'Bad return code: %d   Error executing cxxtestgen: %s' % (status, cmd))
        #
        files = [self.py_cpp]
        for i in [1, 2]:
            args = "--have-eh --abort-on-fail --part Part%s.h" % str(i)
            file = currdir + self.prefix + '_py%s.cpp' % str(i)
            files.append(file)
            if self.cxxtest_import:
                os.chdir(currdir)
                cxxtest.cxxtestgen.main(['cxxtestgen', self.fog, '-o', file] + re.split('[ ]+', args), True)
            else:
                cmd = join_commands(
                    "cd %s" % currdir,
                    "%s %s../bin/cxxtestgen %s -o %s %s > %s 2>&1" % (sys.executable, currdir, self.fog, file, args, self.py_out))
                status = subprocess.call(cmd, shell=True)
                self.assertEqual(status, 0, 'Bad return code: %d   Error executing cxxtestgen: %s' % (status, cmd))
        #
        cmd = join_commands("cd %s" % currdir,
                            "%s %s %s %s. %s%s../ %s > %s 2>&1" % (self.compiler, self.exe_option, self.build_target, self.include_option, self.include_option, currdir, ' '.join(files), self.build_log))
        status = subprocess.call(cmd, shell=True)
        for file in files:
            if os.path.exists(file):
                os.remove(file)
        self.assertEqual(status, 0, 'Bad return code: %d   Error executing command: %s' % (status, cmd))
        #
        cmd = join_commands("cd %s" % currdir,
                            "%s %s -v > %s 2>&1" % (self.valgrind, self.build_target, self.px_pre))
        status = subprocess.call(cmd, shell=True)
        OUTPUT = open(self.px_pre, 'a')
        OUTPUT.write('Error level = ' + str(status) + '\n')
        OUTPUT.close()
        diffstr = file_diff(self.px_pre, currdir + output, self.file_filter)
        if not diffstr == '':
            self.fail("Unexpected differences in output:\n" + diffstr)
        if self.valgrind != '':
            self.parse_valgrind(self.px_pre)
        #
        self.passed = True

    def compile(self, prefix='', args=None, compile='', output=None, main=None, failGen=False, run=None, logfile=None, failBuild=False, init=True):
        """Run cxxtestgen and compile the code that is generated"""
        if init:
            self.init(prefix)
        #
        if self.cxxtest_import:
            try:
                os.chdir(currdir)
                status = cxxtest.cxxtestgen.main(['cxxtestgen', self.fog, '-o', self.py_cpp] + re.split('[ ]+', args), True)
            except:
                status = 1
        else:
            cmd = join_commands(
                "cd %s" % currdir,
                "%s %s../bin/cxxtestgen %s -o %s %s > %s 2>&1" % (sys.executable, currdir, self.fog, self.py_cpp, args, self.py_out))
            status = subprocess.call(cmd, shell=True)
        if failGen:
            if status == 0:
                self.fail('Expected cxxtestgen to fail.')
            else:
                self.passed = True
                return
        if not self.cxxtest_import:
            self.assertEqual(status, 0, 'Bad return code: %d   Error executing command: %s' % (status, cmd))
        #
        if main is not None:
            # Compile with main
            cmd = join_commands("cd %s" % currdir,
                                "%s %s %s %s. %s%s../ %s main.cpp %s > %s 2>&1" % (self.compiler, self.exe_option, self.build_target, self.include_option, self.include_option, currdir, compile, self.py_cpp, self.build_log))
        else:
            # Compile without main
            cmd = join_commands("cd %s" % currdir,
                                "%s %s %s %s. %s%s../ %s %s > %s 2>&1" % (self.compiler, self.exe_option, self.build_target, self.include_option, self.include_option, currdir, compile, self.py_cpp, self.build_log))
        status = subprocess.call(cmd, shell=True)
        if failBuild:
            if status == 0:
                self.fail('Expected compiler to fail.')
            else:
                self.passed = True
                return
        else:
            self.assertEqual(status, 0, 'Bad return code: %d   Error executing command: %s' % (status, cmd))
        #
        if compile == '' and output is not None:
            if run is None:
                cmd = join_commands("cd %s" % currdir,
                                    "%s %s -v > %s 2>&1" % (self.valgrind, self.build_target, self.px_pre))
            else:
                cmd = run % (self.valgrind, self.build_target, self.px_pre)
            status = subprocess.call(cmd, shell=True)
            OUTPUT = open(self.px_pre, 'a')
            OUTPUT.write('Error level = ' + str(status) + '\n')
            OUTPUT.close()
            if logfile is None:
                diffstr = file_diff(self.px_pre, currdir + output, self.file_filter)
            else:
                diffstr = file_diff(currdir + logfile, currdir + output, self.file_filter)
            if not diffstr == '':
                self.fail("Unexpected differences in output:\n" + diffstr)
            if self.valgrind != '':
                self.parse_valgrind(self.px_pre)
            if logfile is not None:
                os.remove(currdir + logfile)
        #
        if compile == '' and output is None and os.path.exists(self.py_cpp):
            self.fail("Output cpp file %s should not have been generated." % self.py_cpp)
        #
        self.passed = True

    #
    # Tests for cxxtestgen
    #

    def test_root_or_part(self):
        """Root/Part"""
        self.check_root(prefix='root_or_part', output="parts.out")

    def test_root_plus_part(self):
        """Root + Part"""
        self.compile(prefix='root_plus_part', args="--error-printer --root --part " + samples, output="error.out")

    def test_wildcard(self):
        """Wildcard input"""
        self.compile(prefix='wildcard', args='../sample/*.h', main=True, output="wildcard.out")

    def test_stdio_printer(self):
        """Stdio printer"""
        self.compile(prefix='stdio_printer', args="--runner=StdioPrinter " + samples, output="error.out")

    def test_paren_printer(self):
        """Paren printer"""
        self.compile(prefix='paren_printer', args="--runner=ParenPrinter " + samples, output="paren.out")

    def test_yn_runner(self):
        """Yes/No runner"""
        self.compile(prefix='yn_runner', args="--runner=YesNoRunner " + samples, output="runner.out")

    def test_no_static_init(self):
        """No static init"""
        self.compile(prefix='no_static_init', args="--error-printer --no-static-init " + samples, output="error.out")

    def test_samples_file(self):
        """Samples file"""
        # Create a file with the list of sample files
        OUTPUT = open(currdir + 'Samples.txt', 'w')
        for line in sorted(glob.glob(sampledir + '*.h')):
            OUTPUT.write(line + '\n')
        OUTPUT.close()
        self.compile(prefix='samples_file', args="--error-printer --headers Samples.txt", output="error.out")
        os.remove(currdir + 'Samples.txt')

    def test_have_std(self):
        """Have Std"""
        self.compile(prefix='have_std', args="--runner=StdioPrinter --have-std HaveStd.h", output="std.out")

    def test_comments(self):
        """Comments"""
        self.compile(prefix='comments', args="--error-printer Comments.h", output="comments.out")

    def test_longlong(self):
        """Long long"""
        self.check_if_supported('longlong.cpp', "Long long is not supported by this compiler")
        self.compile(prefix='longlong', args="--error-printer --longlong=\"long long\" LongLong.h", output="longlong.out")

    def test_int64(self):
        """Int64"""
        self.check_if_supported('int64.cpp', "64-bit integers are not supported by this compiler")
        self.compile(prefix='int64', args="--error-printer --longlong=__int64 Int64.h", output="int64.out")

    def test_include(self):
        """Include"""
        self.compile(prefix='include', args="--include=VoidTraits.h --include=LongTraits.h --error-printer IncludeTest.h", output="include.out")

    def test_nullptr_guards(self):
        """Nullptr Messages"""
        self.compile(prefix='nullptr_guards',
                     args="--error-printer NullPtrGuards.h --have-eh",
                     output="nullptr_guards.out")

    #
    # Template file tests
    #

    def test_preamble(self):
        """Preamble"""
        self.compile(prefix='preamble', args="--template=preamble.tpl " + samples, output="preamble.out")

    def test_activate_all(self):
        """Activate all"""
        self.compile(prefix='activate_all', args="--template=activate.tpl " + samples, output="error.out")

    def test_only_suite(self):
        """Only Suite"""
        self.compile(prefix='only_suite', args="--template=%s../sample/only.tpl %s" % (currdir, samples), run="%s %s SimpleTest > %s 2>&1", output="suite.out")

    def test_only_test(self):
        """Only Test"""
        self.compile(prefix='only_test', args="--template=%s../sample/only.tpl %s" % (currdir, samples), run="%s %s SimpleTest testAddition > %s 2>&1", output="suite_test.out")

    def test_have_std_tpl(self):
        """Have Std - Template"""
        self.compile(prefix='have_std_tpl', args="--template=HaveStd.tpl HaveStd.h", output="std.out")

    def test_exceptions_tpl(self):
        """Exceptions - Template"""
        self.compile(prefix='exceptions_tpl', args="--template=HaveEH.tpl " + self.ehNormals, output="eh_normals.out")

    #
    # Test cases which do not require exception handling
    #

    def test_no_errors(self):
        """No errors"""
        self.compile(prefix='no_errors', args="--error-printer GoodSuite.h", output="good.out")

    def test_infinite_values(self):
        """Infinite values"""
        self.compile(prefix='infinite_values', args="--error-printer --have-std TestNonFinite.h", output="infinite.out")

    def test_max_dump_size(self):
        """Max dump size"""
        self.compile(prefix='max_dump_size', args="--error-printer --include=MaxDump.h DynamicMax.h SameData.h", output='max.out')

    def test_char_assertions(self):
        """char* assertions"""
        self.compile(prefix='char_assertions',
                     args='--error-printer CharAssertions.h',
                     output='char_assertions.out')

    def test_wide_char(self):
        """Wide char"""
        self.check_if_supported('wchar.cpp', "The file wchar.cpp is not supported.")
        self.compile(prefix='wide_char', args="--error-printer WideCharTest.h", output="wchar.out")

    # def test_factor(self):
    #    """Factor"""
    #    self.compile(prefix='factor', args="--error-printer --factor Factor.h", output="factor.out")

    def test_user_traits(self):
        """User traits"""
        self.compile(prefix='user_traits', args="--template=UserTraits.tpl UserTraits.h", output='user.out')

    normals = " ".join(currdir + file for file in ["LessThanEquals.h", "Relation.h", "DefaultTraits.h", "DoubleCall.h", "SameData.h", "SameFiles.h", "Tsm.h", "TraitsTest.h", "MockTest.h", "SameZero.h"])

    def test_normal_behavior_xunit(self):
        """Normal Behavior with XUnit Output"""
        self.compile(prefix='normal_behavior_xunit', args="--xunit-printer " + self.normals, logfile='TEST-cxxtest.xml', output="normal.xml")

    def test_normal_behavior(self):
        """Normal Behavior"""
        self.compile(prefix='normal_behavior', args="--error-printer " + self.normals, output="normal.out")

    def test_normal_plus_abort(self):
        """Normal + Abort"""
        self.compile(prefix='normal_plus_abort', args="--error-printer --have-eh --abort-on-fail " + self.normals, output="abort.out")

    def test_stl_traits(self):
        """STL Traits"""
        self.check_if_supported('stpltpl.cpp', "The file stpltpl.cpp is not supported.")
        self.compile(prefix='stl_traits', args="--error-printer StlTraits.h", output="stl.out")

    def test_normal_behavior_world(self):
        """Normal Behavior with World"""
        self.compile(prefix='normal_behavior_world', args="--error-printer --world=myworld " + self.normals, output="world.out")

    #
    # Test cases which do require exception handling
    #
    def test_throw_wo_std(self):
        """Throw w/o Std"""
        self.compile(prefix='test_throw_wo_std', args="--template=ThrowNoStd.tpl ThrowNoStd.h", output='throw.out')

    ehNormals = "Exceptions.h DynamicAbort.h"

    def test_exceptions(self):
        """Exceptions"""
        self.compile(prefix='exceptions', args="--error-printer --have-eh " + self.ehNormals, output="eh_normals.out")

    def test_exceptions_plus_abort(self):
        """Exceptions plus abort"""
        self.compile(prefix='exceptions', args="--error-printer --abort-on-fail --have-eh DynamicAbort.h DeepAbort.h ThrowsAssert.h", output="eh_plus_abort.out")

    def test_default_abort(self):
        """Default abort"""
        self.compile(prefix='default_abort', args="--error-printer --include=DefaultAbort.h " + self.ehNormals + " DeepAbort.h ThrowsAssert.h", output="default_abort.out")

    def test_default_no_abort(self):
        """Default no abort"""
        self.compile(prefix='default_no_abort', args="--error-printer " + self.ehNormals + " DeepAbort.h ThrowsAssert.h", output="default_abort.out")

    #
    # Global Fixtures
    #

    def test_global_fixtures(self):
        """Global fixtures"""
        self.compile(prefix='global_fixtures', args="--error-printer GlobalFixtures.h WorldFixtures.h", output="gfxs.out")

    def test_gf_suw_fails(self):
        """GF:SUW fails"""
        self.compile(prefix='gf_suw_fails', args="--error-printer SetUpWorldFails.h", output="suwf.out")

    def test_gf_suw_error(self):
        """GF:SUW error"""
        self.compile(prefix='gf_suw_error', args="--error-printer SetUpWorldError.h", output="suwe.out")

    def test_gf_suw_throws(self):
        """GF:SUW throws"""
        self.compile(prefix='gf_suw_throws', args="--error-printer SetUpWorldThrows.h", output="suwt.out")

    def test_gf_su_fails(self):
        """GF:SU fails"""
        self.compile(prefix='gf_su_fails', args="--error-printer GfSetUpFails.h", output="gfsuf.out")

    def test_gf_su_throws(self):
        """GF:SU throws"""
        self.compile(prefix='gf_su_throws', args="--error-printer GfSetUpThrows.h", output="gfsut.out")

    def test_gf_td_fails(self):
        """GF:TD fails"""
        self.compile(prefix='gf_td_fails', args="--error-printer GfTearDownFails.h", output="gftdf.out")

    def test_gf_td_throws(self):
        """GF:TD throws"""
        self.compile(prefix='gf_td_throws', args="--error-printer GfTearDownThrows.h", output="gftdt.out")

    def test_gf_tdw_fails(self):
        """GF:TDW fails"""
        self.compile(prefix='gf_tdw_fails', args="--error-printer TearDownWorldFails.h", output="tdwf.out")

    def test_gf_tdw_throws(self):
        """GF:TDW throws"""
        self.compile(prefix='gf_tdw_throws', args="--error-printer TearDownWorldThrows.h", output="tdwt.out")

    def test_gf_suw_fails_XML(self):
        """GF:SUW fails"""
        self.compile(prefix='gf_suw_fails', args="--xunit-printer SetUpWorldFails.h", output="suwf.out")

    #
    # GUI
    #

    def test_gui(self):
        """GUI"""
        self.compile(prefix='gui', args='--gui=DummyGui %s' % guiInputs, output="gui.out")

    def test_gui_runner(self):
        """GUI + runner"""
        self.compile(prefix='gui_runner', args="--gui=DummyGui --runner=ParenPrinter %s" % guiInputs, output="gui_paren.out")

    def test_qt_gui(self):
        """QT GUI"""
        self.compile(prefix='qt_gui', args="--gui=QtGui GoodSuite.h", compile=self.qtFlags)

    def test_win32_gui(self):
        """Win32 GUI"""
        self.compile(prefix='win32_gui', args="--gui=Win32Gui GoodSuite.h", compile=self.w32Flags)

    def test_win32_unicode(self):
        """Win32 Unicode"""
        self.compile(prefix='win32_unicode', args="--gui=Win32Gui GoodSuite.h", compile=self.w32Flags + ' -DUNICODE')

    def test_x11_gui(self):
        """X11 GUI"""
        self.check_if_supported('wchar.cpp', "Cannot compile wchar.cpp")
        self.compile(prefix='x11_gui', args="--gui=X11Gui GoodSuite.h", compile=self.x11Flags)

    #
    # Tests for when the compiler doesn't support exceptions
    #

    def test_no_exceptions(self):
        """No exceptions"""
        if self.no_eh_option is None:
            self.skipTest("This compiler does not have an exception handling option")
        self.compile(prefix='no_exceptions', args='--runner=StdioPrinter NoEh.h', output="no_eh.out", compile=self.no_eh_option)

    def test_force_no_eh(self):
        """Force no EH"""
        if self.no_eh_option is None:
            self.skipTest("This compiler does not have an exception handling option")
        self.compile(prefix="force_no_eh", args="--runner=StdioPrinter --no-eh ForceNoEh.h", output="no_eh.out", compile=self.no_eh_option)

    #
    # Invalid input to cxxtestgen
    #

    def test_no_tests(self):
        """No tests"""
        self.compile(prefix='no_tests', args='EmptySuite.h', failGen=True)

    def test_missing_input(self):
        """Missing input"""
        self.compile(prefix='missing_input', args='--template=NoSuchFile.h', failGen=True)

    def test_missing_template(self):
        """Missing template"""
        self.compile(prefix='missing_template', args='--template=NoSuchFile.h ' + samples, failGen=True)

    def test_inheritance(self):
        """Test relying on inheritance"""
        self.compile(prefix='inheritance', args='--error-printer InheritedTest.h', output='inheritance_old.out')

    #
    # Tests that illustrate differences between the different C++ parsers
    #

    def test_namespace1(self):
        """Nested namespace declarations"""
        if self.fog == '':
            self.compile(prefix='namespace1', args='Namespace1.h', main=True, failBuild=True)
        else:
            self.compile(prefix='namespace1', args='Namespace1.h', main=True, output="namespace.out")

    def test_namespace2(self):
        """Explicit namespace declarations"""
        self.compile(prefix='namespace2', args='Namespace2.h', main=True, output="namespace.out")

    def test_inheritance(self):
        """Test relying on inheritance"""
        if self.fog == '':
            self.compile(prefix='inheritance', args='--error-printer InheritedTest.h', failGen=True)
        else:
            self.compile(prefix='inheritance', args='--error-printer InheritedTest.h', output='inheritance.out')

    def test_simple_inheritance(self):
        """Test relying on simple inheritance"""
        self.compile(prefix='simple_inheritance', args='--error-printer SimpleInheritedTest.h', output='simple_inheritance.out')

    def test_simple_inheritance2(self):
        """Test relying on simple inheritance (2)"""
        if self.fog == '':
            self.compile(prefix='simple_inheritance2', args='--error-printer SimpleInheritedTest2.h', failGen=True)
        else:
            self.compile(prefix='simple_inheritance2', args='--error-printer SimpleInheritedTest2.h', output='simple_inheritance2.out')

    def test_comments2(self):
        """Comments2"""
        if self.fog == '':
            self.compile(prefix='comments2', args="--error-printer Comments2.h", failBuild=True)
        else:
            self.compile(prefix='comments2', args="--error-printer Comments2.h", output='comments2.out')

    def test_cpp_template1(self):
        """C++ Templates"""
        if self.fog == '':
            self.compile(prefix='cpp_template1', args="--error-printer CppTemplateTest.h", failGen=True)
        else:
            self.compile(prefix='cpp_template1', args="--error-printer CppTemplateTest.h", output='template.out')

    def test_bad1(self):
        """BadTest1"""
        if self.fog == '':
            self.compile(prefix='bad1', args="--error-printer BadTest.h", failGen=True)
        else:
            self.compile(prefix='bad1', args="--error-printer BadTest.h", output='bad.out')

    #
    # Testing path manipulation
    #

    def test_normal_sympath(self):
        """Normal Behavior - symbolic path"""
        files = ["LessThanEquals.h", "Relation.h", "DefaultTraits.h",
                 "DoubleCall.h", "SameData.h", "SameFiles.h", "Tsm.h",
                 "TraitsTest.h", "MockTest.h", "SameZero.h"]

        sympath_name = 'test_sympath'
        sympath_dir = '../%s' % (sympath_name,)
        prefix = 'normal_sympath'

        _files = " ".join(files)
        self.init(prefix=prefix)
        # need to ignore very specific errors here
        if os.path.islink(sympath_name):
            os.remove(sympath_name)
        if os.path.isdir(sympath_dir):
            shutil.rmtree(sympath_dir)
        if not os.path.exists('../.git'):
            raise ValueError("Wrong local directory, run from the tests folder.")
        os.mkdir(sympath_dir)
        os.symlink(sympath_dir, sympath_name)
        self.py_cpp = 'test_sympath/%s_py.cpp' % (prefix,)
        self.compile(prefix=prefix,
                     init=False,
                     args="--error-printer " + _files,
                     output="normal.out")
        os.remove(sympath_name)
        shutil.rmtree(sympath_dir)


    def test_normal_relpath(self):
        """Normal Behavior - relative path"""
        files = ["LessThanEquals.h", "Relation.h", "DefaultTraits.h",
                 "DoubleCall.h", "SameData.h", "SameFiles.h", "Tsm.h",
                 "TraitsTest.h", "MockTest.h", "SameZero.h"]
        _files = " ".join(files)
        prefix = 'normal_relative'
        self.init(prefix=prefix)
        try:
            shutil.rmtree('../test_relpath')
        except:
            pass
        os.mkdir('../test_relpath')
        self.py_cpp = '../test_relpath/' + prefix + '_py.cpp'
        self.compile(prefix=prefix,
                     init=False,
                     args="--error-printer " + _files,
                     output="normal.out")
        shutil.rmtree('../test_relpath')


class TestCpp(BaseTestCase, unittest.TestCase):

    # Compiler specifics
    exe_option = '-o'
    include_option = '-I'
    compiler = 'c++ -Wall -W -Werror -g'
    no_eh_option = None
    qtFlags = '-Ifake'
    x11Flags = '-Ifake'
    w32Flags = '-Ifake'

    def run(self, *args, **kwds):
        if available('c++', '-o'):
            return unittest.TestCase.run(self, *args, **kwds)

    def setUp(self):
        BaseTestCase.setUp(self)

    def tearDown(self):
        BaseTestCase.tearDown(self)


class TestCppFOG(TestCpp):

    fog = '-f'

    def run(self, *args, **kwds):
        if ply_available:
            return TestCpp.run(self, *args, **kwds)


class TestGpp(BaseTestCase, unittest.TestCase):

    # Compiler specifics
    exe_option = '-o'
    include_option = '-I'
    compiler = 'g++ -g -ansi -pedantic -Wmissing-declarations -Werror -Wall -W -Wshadow -Woverloaded-virtual -Wnon-virtual-dtor -Wreorder -Wsign-promo %s' % os.environ.get('CXXTEST_GCOV_FLAGS', '')
    no_eh_option = '-fno-exceptions'
    qtFlags = '-Ifake'
    x11Flags = '-Ifake'
    w32Flags = '-Ifake'

    def run(self, *args, **kwds):
        if available('g++', '-o'):
            return unittest.TestCase.run(self, *args, **kwds)

    def setUp(self):
        BaseTestCase.setUp(self)

    def tearDown(self):
        BaseTestCase.tearDown(self)


class TestGppPy(TestGpp):

    def run(self, *args, **kwds):
        if cxxtest_available:
            self.cxxtest_import = True
            status = TestGpp.run(self, *args, **kwds)
            self.cxxtest_import = False
            return status


class TestGppFOG(TestGpp):

    fog = '-f'

    def run(self, *args, **kwds):
        if ply_available:
            return TestGpp.run(self, *args, **kwds)


class TestGppFOGPy(TestGppFOG):

    def run(self, *args, **kwds):
        if cxxtest_available:
            self.cxxtest_import = True
            status = TestGppFOG.run(self, *args, **kwds)
            self.cxxtest_import = False
            return status


class TestGppValgrind(TestGpp):

    valgrind = 'valgrind --tool=memcheck --leak-check=yes'

    def file_filter(self, file):
        for line in file:
            if line.startswith('=='):
                continue
            # Some *very* old versions of valgrind produce lines like:
            #   free: in use at exit: 0 bytes in 0 blocks.
            #   free: 2 allocs, 2 frees, 360 bytes allocated.
            if line.startswith('free: '):
                continue
            yield normalize_line_for_diff(line)

    def run(self, *args, **kwds):
        if find('valgrind') is None:
            return
        return TestGpp.run(self, *args, **kwds)

    def parse_valgrind(self, fname):
        # There is a well-known leak on Mac OSX platforms...
        if sys.platform == 'darwin':
            min_leak = 16
        else:
            min_leak = 0
        #
        INPUT = open(fname, 'r')
        for line in INPUT:
            if not line.startswith('=='):
                continue
            tokens = re.split('[ \t]+ ', line)
            if len(tokens) < 4:
                continue
            if tokens[1] == 'definitely' and tokens[2] == 'lost:':
                if eval(tokens[3]) > min_leak:
                    self.fail("Valgrind Error: " + ' '.join(tokens[1:]))
            if tokens[1] == 'possibly' and tokens[2] == 'lost:':
                if eval(tokens[3]) > min_leak:
                    self.fail("Valgrind Error: " + ' '.join(tokens[1:]))


class TestGppFOGValgrind(TestGppValgrind):

    fog = '-f'

    def run(self, *args, **kwds):
        if ply_available:
            return TestGppValgrind.run(self, *args, **kwds)


class TestClang(BaseTestCase, unittest.TestCase):

    # Compiler specifics
    exe_option = '-o'
    include_option = '-I'
    compiler = 'clang++ -v -g -Wall -W -Wshadow -Woverloaded-virtual -Wnon-virtual-dtor -Wreorder -Wsign-promo'
    no_eh_option = '-fno-exceptions'
    qtFlags = '-Ifake'
    x11Flags = '-Ifake'
    w32Flags = '-Ifake'

    def run(self, *args, **kwds):
        if available('clang++', '-o'):
            return unittest.TestCase.run(self, *args, **kwds)

    def setUp(self):
        BaseTestCase.setUp(self)

    def tearDown(self):
        BaseTestCase.tearDown(self)


class TestClangFOG(TestClang):

    fog = '-f'

    def run(self, *args, **kwds):
        if ply_available:
            return TestClang.run(self, *args, **kwds)


class TestCL(BaseTestCase, unittest.TestCase):

    # Compiler specifics
    exe_option = '-o'
    include_option = '-I'
    compiler = 'cl -nologo -GX -W4'  # -WX'
    no_eh_option = '-GX-'
    qtFlags = '-Ifake'
    x11Flags = '-Ifake'
    w32Flags = '-Ifake'

    def run(self, *args, **kwds):
        if available('cl', '-o'):
            return unittest.TestCase.run(self, *args, **kwds)

    def setUp(self):
        BaseTestCase.setUp(self)

    def tearDown(self):
        BaseTestCase.tearDown(self)


class TestCLFOG(TestCL):

    fog = '-f'

    def run(self, *args, **kwds):
        if ply_available:
            return TestCL.run(self, *args, **kwds)


if __name__ == '__main__':
    unittest.main()
