import hotshot

import hotshot.log

import os

import pprint

import unittest

import _hotshot

import gc



from test import test_support



from hotshot.log import ENTER, EXIT, LINE





def shortfilename(fn):

    # We use a really shortened filename since an exact match is made,

    # and the source may be either a Python source file or a

    # pre-compiled bytecode file.

    if fn:

        return os.path.splitext(os.path.basename(fn))[0]

    else:

        return fn





class UnlinkingLogReader(hotshot.log.LogReader):

    """Extend the LogReader so the log file is unlinked when we're

    done with it."""



    def __init__(self, logfn):

        self.__logfn = logfn

        hotshot.log.LogReader.__init__(self, logfn)



    def next(self, index=None):

        try:

            return hotshot.log.LogReader.next(self)

        except StopIteration:

            self.close()

            os.unlink(self.__logfn)

            raise





class HotShotTestCase(unittest.TestCase):

    def new_profiler(self, lineevents=0, linetimings=1):

        self.logfn = test_support.TESTFN

        return hotshot.Profile(self.logfn, lineevents, linetimings)



    def get_logreader(self):

        return UnlinkingLogReader(self.logfn)



    def get_events_wotime(self):

        L = []

        for event in self.get_logreader():

            what, (filename, lineno, funcname), tdelta = event

            L.append((what, (shortfilename(filename), lineno, funcname)))

        return L



    def check_events(self, expected):

        events = self.get_events_wotime()

        if events != expected:

            self.fail(

                "events did not match expectation; got:\n%s\nexpected:\n%s"

                % (pprint.pformat(events), pprint.pformat(expected)))



    def run_test(self, callable, events, profiler=None):

        if profiler is None:

            profiler = self.new_profiler()

        self.failUnless(not profiler._prof.closed)

        profiler.runcall(callable)

        self.failUnless(not profiler._prof.closed)

        profiler.close()

        self.failUnless(profiler._prof.closed)

        self.check_events(events)



    def test_addinfo(self):

        def f(p):

            p.addinfo("test-key", "test-value")

        profiler = self.new_profiler()

        profiler.runcall(f, profiler)

        profiler.close()

        log = self.get_logreader()

        info = log._info

        list(log)

        self.failUnless(info["test-key"] == ["test-value"])



    def test_line_numbers(self):

        def f():

            y = 2

            x = 1

        def g():

            f()

        f_lineno = f.func_code.co_firstlineno

        g_lineno = g.func_code.co_firstlineno

        events = [(ENTER, ("test_hotshot", g_lineno, "g")),

                  (LINE,  ("test_hotshot", g_lineno+1, "g")),

                  (ENTER, ("test_hotshot", f_lineno, "f")),

                  (LINE,  ("test_hotshot", f_lineno+1, "f")),

                  (LINE,  ("test_hotshot", f_lineno+2, "f")),

                  (EXIT,  ("test_hotshot", f_lineno, "f")),

                  (EXIT,  ("test_hotshot", g_lineno, "g")),

                  ]

        self.run_test(g, events, self.new_profiler(lineevents=1))



    def test_start_stop(self):

        # Make sure we don't return NULL in the start() and stop()

        # methods when there isn't an error.  Bug in 2.2 noted by

        # Anthony Baxter.

        profiler = self.new_profiler()

        profiler.start()

        profiler.stop()

        profiler.close()

        os.unlink(self.logfn)



    def test_bad_sys_path(self):

        import sys

        import os

        orig_path = sys.path

        coverage = hotshot._hotshot.coverage

        try:

            # verify we require a list for sys.path

            sys.path = 'abc'

            self.assertRaises(RuntimeError, coverage, test_support.TESTFN)

            # verify that we require sys.path exists

            del sys.path

            self.assertRaises(RuntimeError, coverage, test_support.TESTFN)

        finally:

            sys.path = orig_path

            if os.path.exists(test_support.TESTFN):

                os.remove(test_support.TESTFN)



    def test_logreader_eof_error(self):

        self.assertRaises((IOError, EOFError), _hotshot.logreader, ".")

        gc.collect()



def test_main():

    test_support.run_unittest(HotShotTestCase)





if __name__ == "__main__":

    test_main()

