#!/usr/bin/python

# zephyr test suite
#
#   Operates on libraries in the build tree.  To test purely internal
#   interfaces, stuff them in a testing library...


"""Test Suite for libzephyr"""

import optparse
import os
import socket
import ctypes
import ctypes.util
import time
from ctypes import c_int, c_char, POINTER, c_char_p, sizeof

from zephyr_ctypes import *

__revision__ = "$Id: d8a0aca27e6979c9ea682fe654628b557a72c398 $"
try:
    __version__ = "%s/%s" % (__revision__.split()[3], __revision__.split()[2])
except IndexError:
    __version__ = "unknown"

# TODO: pick some real framework later, we're just poking around for now
class TestSuite(object):
    """test collection and runner"""
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

    def run(self):
        tests = sorted([testname for testname in dir(self) 
                        if testname.startswith("test_")])
        failures = []
        for test in tests:
            try:
                print "===", "starting", test, "==="
                getattr(self, test)()
                print "===", "done", test, "==="
            except TestFailure, tf:
                print "===", "FAILED", test, "==="
                failures.append([test, tf])

        return failures


class TestFailure(Exception):
    pass

def py_make_ascii(input):
    """reference ZMakeAscii expressed as python..."""
    hexes = ["%02X" % ord(ch) for ch in input]
    output = []
    for i in range(0, len(hexes), 4):
        output.append("0x" + "".join(hexes[i:i+4]))
    return " ".join(output)

def py_make_zcode(input):
    """reference ZMakeZcode expressed as python..."""
    return "Z" + input.replace("\xff", "\xff\xf1").replace("\0", "\xff\xf0")

def find_libzephyr(builddir='.'):
    # find the library
    libzephyr_paths = ['libzephyr.so', 'libzephyr.dylib']
    libzephyr_paths += [os.path.join('.libs', i) for i in libzephyr_paths]
    libzephyr_paths = [os.path.join(builddir, i) for i in libzephyr_paths]
    libzephyr_paths = [i for i in libzephyr_paths if os.path.exists(i)]
    return libzephyr_paths[0]

class ZephyrTestSuite(TestSuite):
    """Tests for libzephyr"""
    def setup(self):
        self._libzephyr = libZephyr(find_libzephyr(self.builddir))

    def cleanup(self):
        # no cleanup needed yet
        pass

    def test_zinit(self):
        """test that ZInitialize did something"""
        print "fd", self._libzephyr.ZGetFD()
        realm = self._libzephyr.ZGetRealm()
        print "realm", realm
        if not realm:
            raise TestFailure("empty realm %s" % realm)
        if self._libzephyr.Zauthtype and realm == 'local-realm':
            raise TestFailure("useless realm %s" % realm)
        if self._libzephyr.Zauthtype == 0 and realm != 'local-realm':
            raise TestFailure("wrong realm %s (should be local-realm)" % realm)
        print self._libzephyr.ZGetSender()
        
    def test_notices(self):
        """test notice construct/destruct"""
        notice = ZNotice_t()
        print "sizeof ZNotice_t", sizeof(notice)
        zbuf = c_char_p(0)
        zbuflen = c_int(0)
        st = self._libzephyr.ZFormatNotice(notice, zbuf, zbuflen, ZNOAUTH)
        print "ZFormatNotice:", "retval", st
        print "\tzbuflen", zbuflen
        print "\tzbuf", repr(zbuf.value)
        new_notice = ZNotice_t()
        st = self._libzephyr.ZParseNotice(zbuf, zbuflen, new_notice)
        print "ZParseNotice:", "retval", st
        print "\tz_version", new_notice.z_version
        ctypes_pprint(new_notice)

    def test_z_compare_uid(self):
        """test ZCompareUID"""

        uid1 = ZUnique_Id_t()
        uid2 = ZUnique_Id_t()
        assert self._libzephyr.ZCompareUID(uid1, uid2), "null uids don't match"
        
        # there's no ZUnique_Id_t constructor - Z_FormatHeader and Z_NewFormatHeader initialize notice->z_uid directly
        notice1 = ZNotice_t()
        zbuf = c_char_p(0)
        zbuflen = c_int(0)
        st = self._libzephyr.ZFormatNotice(notice1, zbuf, zbuflen, ZNOAUTH)
        assert st == 0, "ZFormatNotice notice1 failed"

        notice2 = ZNotice_t()
        zbuf = c_char_p(0)
        zbuflen = c_int(0)
        st = self._libzephyr.ZFormatNotice(notice2, zbuf, zbuflen, ZNOAUTH)
        assert st == 0, "ZFormatNotice notice2 failed"

        assert not self._libzephyr.ZCompareUID(notice1.z_uid, notice2.z_uid), "distinct notices don't compare as distinct"
        # ctypes_pprint(notice1.z_uid)

    def test_zauthtype(self):
        """Make sure Zauthtype is an acceptable value"""
        assert self._libzephyr.Zauthtype in (0, 4, 5)

    def test_z_expand_realm(self):
        """test ZExpandRealm"""
        if self._libzephyr.Zauthtype:
            assert self._libzephyr.ZExpandRealm("") == ""
            assert self._libzephyr.ZExpandRealm("localhost") == ""
            assert self._libzephyr.ZExpandRealm("bitsy.mit.edu") == "ATHENA.MIT.EDU"
        else:
            assert self._libzephyr.ZExpandRealm("") == ""
            assert self._libzephyr.ZExpandRealm("localhost") == socket.getfqdn("localhost").upper()
            assert self._libzephyr.ZExpandRealm("bitsy.mit.edu") == "BITSY.MIT.EDU"

def find_buildpath():
    parser = optparse.OptionParser(usage=__doc__,
                                   version = "%%prog %s" % __version__)
    parser.add_option("--builddir", default="..", 
                      help="where to find the top of the build tree")
    parser.add_option("--verbose", "-v", action="store_true",
                      help="pass through for doctest.testfile")
    opts, args = parser.parse_args()
    assert not args, "no args yet"

    return os.path.join(opts.builddir, "lib")

def getsockname(fd):
    """wrapped C lib getsocketname (works on raw fd)"""
    libc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("c"))

    call_getsockname = libc.getsockname
    call_getsockname.argtypes = [
        c_int,                 # int s
        POINTER(sockaddr),     # struct sockaddr *name
        POINTER(c_int),        # socklen_t *namelen
        ]
    name = sockaddr(0)
    namelen = c_int(sizeof(name))
    ret = call_getsockname(fd, name, namelen)
    if ret == 0:
        return name
    # we can't get at errno until python 2.6...
    print ret
    raise EnvironmentError("getsockname failed")


if __name__ == "__main__":
    tester = ZephyrTestSuite(builddir=find_buildpath())
    tester.setup()
    failures = tester.run()
    tester.cleanup()
    for failure, exc in failures:
        print "FAIL:", failure, str(exc)
