itpog.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. '''
  2. Tipog tests
  3. Usage:
  4. itpog [-v] <filename> [-o <output>]
  5. Options:
  6. -v --verbose Verbose print
  7. -o --output Register the results in <output> file (.json)
  8. -h --help Show this screen.
  9. --version Show version.
  10. '''
  11. import cProfile
  12. from importlib import import_module
  13. import json
  14. import os
  15. import timeit
  16. from docopt import docopt
  17. import yaml
  18. __version__ = "0.2"
  19. class ItpogDataError(IOError):
  20. pass
  21. class ItPog(object):
  22. def __init__(self, ipfile, verbose=False, output=None, outfilename=None):
  23. try:
  24. with open(ipfile, "r") as f:
  25. data = yaml.load(f)
  26. except FileNotFoundError:
  27. raise FileNotFoundError("no file named '{}'".format(ipfile))
  28. except yaml.scanner.ScannerError:
  29. raise ItpogDataError("unreadable yaml file '{}'".format(ipfile))
  30. if not data:
  31. raise ItpogDataError("file '{}' is empty".format(ipfile))
  32. self.ipfile = ipfile
  33. self.ipdata = data
  34. self.verbose = verbose
  35. self.output = output
  36. self.outfilename = outfilename
  37. self.out_result = []
  38. @classmethod
  39. def run_file(cls, *args, **kwargs):
  40. """ convenient method to load a file and run the tests
  41. returns an ItPog object"""
  42. try:
  43. itpog = cls(*args, **kwargs)
  44. itpog.run()
  45. except Exception as e:
  46. print("{}: {}".format(e.__class__.__name__, str(e)))
  47. return itpog
  48. def run(self):
  49. """ run the jobs as programmed in the given file """
  50. for name, imp in self.ipdata["imports"].items():
  51. try:
  52. globals()[name] = import_module(imp)
  53. except (ModuleNotFoundError, ImportError):
  54. raise ItpogDataError("unable to import '{}'".format(imp))
  55. try:
  56. jobs = self.ipdata["jobs"]
  57. except KeyError:
  58. raise ItpogDataError("missing 'jobs' entry")
  59. for function_name, job in jobs.items():
  60. try:
  61. self._run_job(function_name, job)
  62. except Exception as e:
  63. print("{}: {}".format(e.__class__.__name__, str(e)))
  64. if self.output:
  65. if not self.outfilename:
  66. self.outfilename = "{}_result.json".format(os.path.splitext(self.ipfile)[0])
  67. outfilepath = os.path.join(".", self.outfilename)
  68. try:
  69. os.remove(outfilepath)
  70. except FileNotFoundError:
  71. pass
  72. with open(outfilepath, "w+") as f:
  73. json.dump(self.out_result, f)
  74. print("** End of the tests")
  75. def _run_job(self, function_name, job):
  76. try:
  77. function = eval(function_name)
  78. except NameError:
  79. raise NameError("unknown function ('{}')".format(function_name))
  80. if self.verbose:
  81. print("** Test function '{}'".format(function_name))
  82. try:
  83. args_lst = job["args"]
  84. except KeyError:
  85. args_lst = [[]]
  86. try:
  87. validator_str = job["validator"]
  88. try:
  89. validator = eval(validator_str)
  90. if self.verbose:
  91. print("> validator: '{}'".format(validator_str))
  92. except NameError:
  93. raise ItpogDataError("unknown function as validator ('{}')".format(validator_str))
  94. except (TypeError, KeyError, SyntaxError):
  95. validator = None
  96. for args in args_lst:
  97. call_str = "{}(*{})".format(function_name, args)
  98. print(">> {}".format(call_str))
  99. exectime = self.ittime(call_str)
  100. print("\t> Run in {} ms.".format(exectime))
  101. if self.verbose:
  102. self.profile(call_str)
  103. if validator:
  104. valid = self.validate(function, validator, args)
  105. print("\t> Validated: {}".format(valid))
  106. if self.output:
  107. last_result = {"call": call_str, "result": function(*args), "exectime": exectime}
  108. try:
  109. last_result.update(job["infos"])
  110. except KeyError:
  111. pass
  112. self.out_result.append(last_result)
  113. if self.verbose:
  114. print("------------------------------")
  115. @staticmethod
  116. def profile(_call):
  117. """ Use the cProfile module to measure the execution time of each call
  118. _call has to be a string
  119. """
  120. cProfile.run(_call, sort='nfl')
  121. @staticmethod
  122. def ittime(_call):
  123. """ returns the execution time in milli-seconds
  124. _call has to be a string
  125. (ex: 'time.sleep(1)', which will return 1000)
  126. """
  127. number, t = 1, 0
  128. while t < 10 ** 8:
  129. t = timeit.timeit(lambda: eval(_call), number=number)
  130. if t >= 0.1:
  131. return 1000 * t / number
  132. number *= 10
  133. else:
  134. return -1
  135. @staticmethod
  136. def validate(fct, validator, args=[], kwargs={}):
  137. """ compare the results of 'fct' and 'validator' methods,
  138. with the same 'args' and 'kwargs' arguments """
  139. result = fct(*args, **kwargs)
  140. attended = validator(*args, **kwargs)
  141. return result == attended
  142. @staticmethod
  143. def generate_sample(filename="itpog_sample_file.yml"):
  144. """ generate a sample yaml configuration file """
  145. data = {"imports": {"time": "time", "math": "math"},
  146. "jobs": {
  147. "time.sleep": {"infos": {"note": "x second attended"},
  148. "args": [[0.1], [1]],
  149. "validator": ""},
  150. "math.pow": {"infos": {},
  151. "args": [[1, 2], [3, 2], [3, 3], [4, 2]],
  152. "validator": "lambda x, y: x**y"}
  153. }
  154. }
  155. with open(os.path.join(".", filename), "w+") as f:
  156. yaml.dump(data, f)
  157. if __name__ == "__main__":
  158. sysargs = docopt(__doc__, version=__version__)
  159. itpog = ItPog.run_file(sysargs["<filename>"], sysargs["--verbose"], sysargs["--output"], sysargs["<output>"])