#!/usr/bin/python

# test_history.py
#       --copyright--                   Copyright 2007 (C) Tranzoa, Co. All rights reserved.    Warranty: You're free and on your own here. This code is not necessarily up-to-date or of public quality.
#       --url--                         http://www.tranzoa.net/tzpython/
#       --email--                       pycode is the name to send to. tranzoa.com is the place to send to.
#       --bodstamps--
#       January 19, 2008        bar
#       January 23, 2008        bar     only forget_results on new versions
#       February 2, 2008        bar     out_of_date()
#       February 3, 2008        bar     sort out the resulted tests by name, too
#       March 17, 2008          bar     c_string the __str__ output
#       May 17, 2008            bar     email adr
#       November 29, 2011       bar     pyflake cleanup
#       May 27, 2012            bar     doxygen namespace
#       June 2, 2013            bar     proper interpretor
#       --eodstamps--
##      \file
#       \namespace              tzpython.test_history
#
#
#       Keep track of tests - which ones have been done, when, by whom, etc.
#
#       Used to track compliance with Software Requirements Specification ( SRS ) tests.
#       Used for regression test tracking.
#
#

import  time

import  tzlib



class   a_version(object)   :

    def __init__(me, name, description = None) :
        me.name             = (name or "").strip()
        me.description      = (description or "").strip()

    def __str__(me) :
        return(("%s : %s" % ( tzlib.c_string((me.name or "").strip()), tzlib.c_string((me.description or "").strip()) )).strip(" :") )

    pass                    # a_program_version



class   a_result(object)    :

    def __init__(me, passed = True, name = None, description = None) :
        me.when             = time.time()
        me.name             = (name        or "").strip()
        me.passed           = not not passed
        me.description      = (description or "").strip()

    def __str__(me) :
        ps                  = "Failed"
        if  me.passed       :
            ps              = "Passed"
        return(("%s %s : %s : %s" % ( ps, time.asctime(time.localtime(me.when)), tzlib.c_string((me.name or "").strip()), tzlib.c_string((me.description or "").strip()) )).strip(" :").replace(": :", ":") )

    pass                    # a_result



class   a_test(object)  :

    def __init__(me, name, description = None) :
        me.name             = (name        or "").strip()
        me.description      = (description or "").strip()
        me.results          = []                # order of addition

    def add_result(me, passed = True, name = None, description = None) :
        me.results.append(a_result(passed, name, description))

    def __str__(me) :
        s                   = ("%s : %s" % ( tzlib.c_string((me.name or "").strip()), tzlib.c_string((me.description or "").strip()) )).strip(" :")
        for i in xrange(len(me.results) - 1, -1, -1) :
            s              += "\n" + "        " + str(me.results[i])
        return(s)

    pass                    # a_test





class   a_test_history(object) :
    """ Tracks testing history by test ids (SRS numbers, for example). """

    def __init__(me) :
        me.create_when      = time.time()
        me.latest_when      = me.create_when

        me.versions         = {}            # keyed by name
        me.tests            = {}            # keyed by name



    def out_of_date(me) :
        me.latest_when      = max(me.latest_when, time.time())



    def version(me, name, description = None) :
        """
            Get/set/add a version.

            Sets the version description if 'description' is not None (and 'name' is not None).

            If a new version or description added, all results are forgotten.

            Return "" (if no name) or previous description or 'description' if this is a new version.
        """

        ov  = ""
        if  name :
            if  name not in me.versions :
                me.versions[name]   = a_version(name, description)
                me.out_of_date()
                me.forget_results()

            ov                      = me.versions[name].description

            if  description        != None :
                description                         = description.strip()
                if me.versions[name].description   != description :
                    me.versions[name].description   = description
                    me.out_of_date()
                    me.forget_results()
                pass
            pass

        return(ov)



    def is_new_version(me, name, description = None) :
        """
            Is this new version information?
        """

        if  name :
            if  name not in me.versions :
                return(True)

            if  description        != None :
                description                         = description.strip()
                if me.versions[name].description   != description :
                    return(True)
                pass
            pass

        return(False)



    def results_count(me) :
        """ Return the number of results we know of. """

        cnt         = 0
        for t in me.tests.values() :
            cnt    += len(t.results)

        return(cnt)





    def add_test(me, name, description = None) :
        """
            Add a test to the testing history.

            If a new test is added or the description changes, all results are forgotten.

            Return the a_test for the test, if any.
        """

        if  name :
            if  name not in me.tests            :
                me.tests[name]                  = a_test(name, description)
                me.out_of_date()

            if  description != None             :
                description                     = description.strip()
                if  me.tests[name].description != description :
                    me.tests[name].description  = description
                    me.out_of_date()
                pass

            return(me.tests[name])

        return(None)



    def add_result(me, test_name, passed = True, name = None, description = None) :
        """
            Add a result to the given test.

            If a new test is added, all results are forgotten.

            Return the (possibly new) test object (or None if it has no name).
        """

        t   = me.add_test(test_name)
        if  t :
            t.add_result(passed, name, description)
            me.out_of_date()

        return(t)


    def forget_results(me) :
        """ Forget all known test results. """

        for t in me.tests.values() :
            t.results   = []
        me.out_of_date()


    def forget_test(me, name) :
        """
            Forget this test.

            All known test results for all tests are forgotten, too.
        """

        if  name and name in me.tests :
            del(me.tests[name])
        pass



    def versions_by_name(me) :
        def _cmp_rtn(v1, v2) :
            return(cmp(v1.name, v2.name))

        rv  = me.versions.values()
        rv.sort(_cmp_rtn)

        return(rv)




    def _ordered_tests(me, cmp_rtn) :
        """ Return an ordered array of the current tests. """

        rv  = me.tests.values()
        rv.sort(cmp_rtn)

        return(rv)



    def tests_by_name(me) :
        """ Return an array of a_test's ordered by ascending name. """

        def _cmp_rtn(t1, t2) :
            return(cmp(t1.name, t2.name))

        return(me._ordered_tests(_cmp_rtn))


    def tests_by_latest_result(me) :
        """ Return an array of a_test's ordered by descending date/time of most recent results. (Un-resulted tests sort by ascending name to the end.) """

        def _cmp_rtn(t1, t2) :
            if  t1.results and not t2.results :
                return(-1)                          # put t1 ahead of t2
            if  t2.results :
                if  not t1.results :
                    return( 1)                      # put t2 ahead of t1
                i   = cmp(t1.name, t2.name)
                if  i :
                    return(i)

                return(cmp(t2.results[-1].when, t1.results[-1].when))

            return(cmp(t1.name, t2.name))

        return(me._ordered_tests(_cmp_rtn))



    def __str__(me) :
        s       = ""

        s      += "Created      : " + time.asctime(time.localtime(me.create_when)) + "\n"
        s      += "Latest result: " + time.asctime(time.localtime(me.latest_when)) + "\n"

        a       = me.versions_by_name()
        if  a   :
            s  += "Versions:\n"
            for v in a :
                s  += "    " + str(v) + "\n"
            pass

        a       = me.tests_by_name()
        if  a   :
            s  += "\nTests by name:\n"
            for t in a :
                s  += "    " + str(t) + "\n"
            pass

        a       = me.tests_by_latest_result()
        if  a   :
            s  += "\nTests by results:\n"
            for t in a :
                s  += "    " + str(t) + "\n"
            pass

        return(s)



    pass        # a_test_history





#
#
#       Test
#
#
if __name__ == '__main__' :
    import  sys

    import  TZCommandLineAtFile


    del(sys.argv[0])

    TZCommandLineAtFile.expand_at_sign_command_line_files(sys.argv)

    t   = a_test_history()

    if  t.version("Program 1") != "" :
        raise "Ooops!"
    time.sleep(1.0)
    if  t.version("Program 1",    "v1.23") != "":
        raise "Ooops!"
    if  t.version("Program 1") != "v1.23" :
        raise "Ooops!"

    t.version("Program 2", "v00.00")

    t.add_test(  "1234", "test number 1234")
    t.add_test(  "5432", "test number 5432")
    t.add_result("0001", False, "failed test")

    time.sleep(1.0)
    t.add_result("1234")
    t.add_result("1234", True,  "passed  ", "this is the second result")

    time.sleep(1.0)
    t.add_result("1234", False,  "failed", "this is the first failure or this test")

    print t


#
#
#
# eof
