common.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. #
  2. # Metrix++, Copyright 2009-2013, Metrix++ Project
  3. # Link: http://metrixplusplus.sourceforge.net
  4. #
  5. # This file is a part of Metrix++ Tool.
  6. #
  7. # Metrix++ is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, version 3 of the License.
  10. #
  11. # Metrix++ is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with Metrix++. If not, see <http://www.gnu.org/licenses/>.
  18. #
  19. import inspect
  20. import os.path
  21. import subprocess
  22. import logging
  23. import difflib
  24. import unittest
  25. import shutil
  26. class ToolRunner(object):
  27. def __init__(self,
  28. tool_name,
  29. opts_list,
  30. dirs_list = None,
  31. cwd='sources',
  32. prefix = "default",
  33. exit_code = 0,
  34. save_prev = False,
  35. use_prev = False,
  36. check_stderr = None,
  37. remove_exiting_dbfile = None,
  38. remove_exiting_dbfile_prev = False):
  39. self.message = ""
  40. # identify gold_file_location
  41. curframe = inspect.currentframe()
  42. calframe = inspect.getouterframes(curframe, 2)
  43. test_name = calframe[1][3]
  44. suite_name = os.path.splitext(os.path.basename(calframe[1][1]))[0]
  45. group_name = os.path.basename(os.path.dirname(calframe[1][1]))
  46. self.suite_location = os.path.join('tests', group_name, suite_name)
  47. self.test_location = os.path.join(self.suite_location, test_name + "_" + tool_name + "_" + str(prefix))
  48. db_file = os.path.join(os.environ['METRIXPLUSPLUS_INSTALL_DIR'], self.suite_location, test_name + ".db")
  49. self.dbfile = db_file
  50. if (remove_exiting_dbfile == True or (remove_exiting_dbfile == None and tool_name == 'collect')) and os.path.exists(db_file):
  51. os.unlink(db_file)
  52. db_file_prev = os.path.join(os.environ['METRIXPLUSPLUS_INSTALL_DIR'], self.suite_location, test_name + ".prev.db")
  53. self.dbfile_prev = db_file_prev
  54. if (remove_exiting_dbfile_prev == True or (remove_exiting_dbfile_prev == None and tool_name == 'collect')) and os.path.exists(db_file_prev):
  55. os.unlink(db_file_prev)
  56. self.cwd = cwd
  57. db_opts = ['--general.db-file=' + db_file]
  58. if use_prev == True:
  59. db_opts.append('--general.db-file-prev=' + db_file_prev)
  60. self.dbopts = db_opts
  61. self.dirs_list = []
  62. if dirs_list != None:
  63. for each in dirs_list:
  64. self.dirs_list = [each]
  65. self.call_args = ['python', os.path.join(os.environ['METRIXPLUSPLUS_INSTALL_DIR'], tool_name + ".py")] \
  66. + db_opts + opts_list + ['--'] + self.dirs_list
  67. self.cmd = " ".join(self.call_args)
  68. self.exit_code_expected = exit_code
  69. self.stderr_lines = check_stderr
  70. self.save_prev = save_prev
  71. def run(self):
  72. child = subprocess.Popen(self.call_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
  73. cwd=os.path.join(self.suite_location, self.cwd))
  74. (child_stdout, child_stderr) = child.communicate()
  75. self.exit_code = child.returncode
  76. gold_file_stdout = self.test_location + "_stdout.gold.txt"
  77. real_file_stdout = self.test_location + "_stdout.real.txt"
  78. diff_file_stdout = self.test_location + "_stdout.diff.html"
  79. gold_file_stderr = self.test_location + "_stderr.gold.txt"
  80. real_file_stderr = self.test_location + "_stderr.real.txt"
  81. diff_file_stderr = self.test_location + "_stderr.diff.html"
  82. # Regenerate gold files if it was requested
  83. if os.environ['METRIXPLUSPLUS_TEST_GENERATE_GOLDS'] == "True":
  84. f = open(gold_file_stdout, 'wb');
  85. f.write(child_stdout);
  86. f.close()
  87. if self.stderr_lines != None:
  88. f = open(gold_file_stderr, 'wb');
  89. f.write(child_stderr);
  90. f.close()
  91. # Match with gold
  92. self.is_stdout_matched = self.inetrnal_compare_with_gold(child_stdout, gold_file_stdout, real_file_stdout, diff_file_stdout)
  93. if self.stderr_lines != None:
  94. self.is_stderr_matched = self.inetrnal_compare_with_gold(child_stderr, gold_file_stderr, real_file_stderr, diff_file_stderr, self.stderr_lines)
  95. else:
  96. self.is_stderr_matched = None
  97. if self.is_stdout_matched == False:
  98. f = open(real_file_stderr, 'wb');
  99. f.write(child_stderr);
  100. f.close()
  101. if self.save_prev == True:
  102. shutil.copy2(self.dbfile, self.dbfile_prev)
  103. return self
  104. def inetrnal_compare_with_gold(self, text, gold_file, real_file, diff_file, lines = None):
  105. if os.path.exists(gold_file) == False:
  106. self.message += "\nGold file does not exist: " + gold_file
  107. return False
  108. f = open(gold_file, 'rb');
  109. gold_text = f.read();
  110. f.close()
  111. gold_to_compare = gold_text
  112. text_to_compare = text
  113. if lines != None:
  114. gold_to_compare = ""
  115. text_to_compare = ""
  116. gold_lines = gold_text.splitlines(True)
  117. text_lines = text.splitlines(True)
  118. for each in lines:
  119. gold_to_compare += "".join(gold_lines[each[0] : each[1]])
  120. text_to_compare += "".join(text_lines[each[0] : each[1]])
  121. result = (gold_to_compare == text_to_compare)
  122. if result == False:
  123. f = open(real_file, 'wb');
  124. f.write(text);
  125. f.close()
  126. diff_text = difflib.HtmlDiff().make_file(gold_to_compare.splitlines(), text_to_compare.splitlines(), "Gold file", "Real output")
  127. f = open(diff_file, 'w');
  128. f.write(diff_text);
  129. f.close()
  130. else:
  131. if os.path.exists(real_file):
  132. os.unlink(real_file)
  133. if os.path.exists(diff_file):
  134. os.unlink(diff_file)
  135. return result
  136. def check_exit_code(self):
  137. return self.exit_code == self.exit_code_expected
  138. def check_stdout(self):
  139. return self.is_stdout_matched
  140. def check_stderr(self):
  141. if self.is_stderr_matched == None:
  142. return True
  143. return self.is_stderr_matched
  144. def check_all(self):
  145. result = self.check_exit_code() and self.check_stdout() and self.check_stderr()
  146. if result == False:
  147. self.message += "\nCheck for exit code: " + str(self.check_exit_code()) \
  148. + ", gold: " + str(self.exit_code_expected) + ", real: " + str(self.exit_code) + \
  149. "\nCheck for stdout: " + str(self.check_stdout()) + "\nCheck for stderr: " + str(self.check_stderr())
  150. return result
  151. def get_message(self):
  152. return self.message
  153. def get_cmd(self):
  154. return self.cmd
  155. def get_description(self):
  156. return self.get_message() + "\nProcess: " + self.get_cmd() + "\nCWD: " + self.cwd
  157. def get_dbfile(self):
  158. return self.dbfile
  159. def get_dbfile_prev(self):
  160. return self.dbfile_prev
  161. class TestCase(unittest.TestCase):
  162. def setUp(self):
  163. logging.basicConfig(format="[TEST-LOG]: %(levelname)s:\t%(message)s", level=logging.WARN)
  164. log_level = os.environ['METRIXPLUSPLUS_LOG_LEVEL']
  165. if log_level == 'ERROR':
  166. log_level = logging.ERROR
  167. elif log_level == 'WARNING':
  168. log_level = logging.WARNING
  169. elif log_level == 'INFO':
  170. log_level = logging.INFO
  171. elif log_level == 'DEBUG':
  172. log_level = logging.DEBUG
  173. else:
  174. raise AssertionError("Unhandled choice of log level")
  175. logging.getLogger().setLevel(log_level)
  176. self.runners = []
  177. def assertExec(self, runner):
  178. # keep reference, so files are not removed during test case time
  179. self.runners.append(runner)
  180. self.assertTrue(runner.check_all(), runner.get_description())
  181. def run(self, result=None):
  182. self.current_result = result # remember result for use in tearDown
  183. unittest.TestCase.run(self, result)
  184. def tearDown(self):
  185. unittest.TestCase.tearDown(self)
  186. if self.current_result.wasSuccessful() == True:
  187. for each in self.runners:
  188. if each.check_all() == True:
  189. if os.path.exists(each.get_dbfile()):
  190. os.unlink(each.get_dbfile())
  191. if os.path.exists(each.get_dbfile_prev()):
  192. os.unlink(each.get_dbfile_prev())