#!/usr/bin/env python
# encoding: utf-8
'''
runtest -- run unit test modules

runtest is a command line tool to run one or multiple unit test modules under a project

@author:     sunmoonone
'''

import sys
import os
import os.path as ospath

from optparse import OptionParser
import re
import unittest
import traceback
from importlib import import_module
import yaml

__all__ = []
__version__ = 0.1
__date__ = '2015-07-07'
__updated__ = '2015-07-07'

DEBUG = 0
TESTRUN = 0
PROFILE = 0

_cwd_ = os.curdir
_mod_=[]

def walk(path,visit,arg):
    '''Calls the function visit with arguments (arg, dirname, item) for each item in the directory tree rooted at path (including path itself, if it is a directory).
    The argument dirname specifies the visited directory,
    the argument item is the current item in the directory (gotten from os.listdir(dirname)).

    if function visit returns false then `item` will not be visited
    if function visit returns true then walk is aborted
    '''
    for f in os.listdir(path):
        ret = visit(arg,path,f)
        if ret==False:
            continue
        elif ret==True:
            return 0
        if ospath.isdir(ospath.join(path,f)):
            if walk(ospath.join(path,f), visit, arg)==0:
                return 0

def match(r,d,f):
    global _mod_
    if d.find('.git')!=-1:
        return False
    f = ospath.join(d,f)
    if r.search(f):
        _mod_.append(f)


def search_module(hint):
    '''
    >>> search_module('parser')
    >>> '' 
    '''
    global _mod_
    _mod_=[]
    
    pattern = hint.replace('*','.*')+'.*\.py$'
    ps = pattern.split('/')
    if not 'test_' in ps[-1]:
        ps[-1]='.*test_'+ps[-1]
    pattern='/'.join(ps)
#     print 'pattern:',pattern
    r = re.compile(pattern,re.IGNORECASE)
    walk('.',match,r)
    
    for i in range(len(_mod_)):
        _mod_[i] = (_mod_[i].lstrip('./')[0:-3]).replace('/','.')
    return _mod_


def runtest(module_name, opts,method=None):
    module = import_module(module_name)
    if hasattr(module, '__requires__'):
        requires=getattr(module, '__requires__')
        for r in requires:
            print 'testing dependency: %s' % r
            runtest(r,opts)
 
    suits=[]
    if method:
        suits.append(unittest.TestLoader().loadTestsFromName(method, module))
    else:
        for name in dir(module):
            obj = getattr(module, name)
            if isinstance(obj, type) and issubclass(obj, unittest.TestCase):
                suits.append(unittest.TestLoader().loadTestsFromTestCase(obj))

    unittest.TextTestRunner(failfast=(opts.failfast), verbosity=(2 if opts.verbose else 1)).run(unittest.TestSuite(suits))


def main(argv=None):
    '''Command line options.'''
#     global _cwd_
#     _cwd_ = ospath.dirname(ospath.abspath(sys.argv[0]))
#     os.chdir(_cwd_)

    program_name = os.path.basename(sys.argv[0])
    program_version = "v0.1"
    program_build_date = "%s" % __updated__

    program_version_string = '%%prog %s (%s)' % (program_version, program_build_date)
    program_usage = '''usage: %s [options] <module_name_hint[:(class|class.method|function)]>''' % program_name# optional - will be autogenerated by optpar
    program_longdesc = '''''' # optional - give further explanation about what the program does
    program_license = "Copyright 2015 sunmoonone                                            \
                Licensed under the Apache License 2.0\nhttp://www.apache.org/licenses/LICENSE-2.0"

    if argv is None:
        argv = sys.argv[1:]
    try:
        # setup option parser
        parser = OptionParser(usage=program_usage,version=program_version_string, epilog=program_longdesc, description=program_license)
        parser.add_option("-c", "--confirm", dest="confirm", action="store_true",help="confirm module found before running test [default: %default]", metavar="CONFIRM")
        parser.add_option("-e", "--env", dest="env", help="environment variables fromat: name=value;name2=value", metavar="ENV")
        parser.add_option("-v", "--verbose", dest="verbose", action="store_true", help="run test verbosely [default: %default]")
        parser.add_option("-f", "--failfast", dest="failfast", action="store_true", help="Stop the test run on the first error or failure [default: %default]")

        # set defaults
        # parser.set_defaults(outfile="./out.txt", infile="./in.txt", verbose=False, confirm=False, failfast=False)

        (opts, args) = parser.parse_args(argv)

        # MAIN BODY #
        if not args:
            raise Exception('missing module name')
            return 1
        
        #set env
        if opts.env:
            pairs=opts.env.split(';')
            for k,v in [p.split('=') for p in pairs]:
                os.environ[k]=v
        
        
        hint=args[0]    
        if hint.find(':')!=-1:
            hint,method=hint.split(':')
        else:
            method=None
            
        #search module in cwd
        mod = search_module(hint)
        count=len(mod)
        if count>1:
            for i in range(count):
                print '%s. %s' % (i+1,mod[i])
            print '0. abort'
            no = raw_input('found %d modules, type the number to choose one:' % count)
            while True:
                try:
                    no = int(no)
                except:
                    no=-1
                if no<0 or no >count:
                    no= raw_input('type the right number:')
                else:
                    if no==0:
                        return 0
                    mod=mod[no-1]
                    break


        elif not mod:
            print 'no module found'
            return 1
        else:
            mod=mod[0]
            if opts.confirm:
                an = raw_input('run: %s ?y/n ' % mod)
                if an!='y':
                    return 0
                
        print '~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*~~~~*'
        mod=mod.lstrip(ospath.sep)
        print 'testing %s' % mod

        if os.path.exists(os.path.join(_cwd_,'unittest.yml')):
            with open(os.path.join(_cwd_,'unittest.yml')) as f:
                conf=yaml.load(f)
                if conf and 'startup' in conf:
                    if '.py' in conf['startup']:
                        _startup = import_module(conf['startup'][:-3])
                    else:
                        _startup = import_module(conf['startup'])

        mod=mod.replace(ospath.sep,'.')
        runtest(mod,opts, method)

    except Exception, e:
        indent = len(program_name) * " "
        sys.stderr.write(program_name + ": " + repr(e) + "\n")
        sys.stderr.write('\n'.join(traceback.format_exception(*(sys.exc_info()))))
        sys.stderr.write(indent + "  for help use --help\n")
        return 2


if __name__ == "__main__":
    if DEBUG:
        sys.argv.append("-h")
    if TESTRUN:
        import doctest
        doctest.testmod()
    if PROFILE:
        import cProfile
        import pstats
        profile_filename = 'runtest_profile.txt'
        cProfile.run('main()', profile_filename)
        statsfile = open("profile_stats.txt", "wb")
        p = pstats.Stats(profile_filename, stream=statsfile)
        stats = p.strip_dirs().sort_stats('cumulative')
        stats.print_stats()
        statsfile.close()
        sys.exit(0)
    sys.exit(main())