pbs_covutils.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. # coding: utf-8
  2. # Copyright (C) 1994-2018 Altair Engineering, Inc.
  3. # For more information, contact Altair at www.altair.com.
  4. #
  5. # This file is part of the PBS Professional ("PBS Pro") software.
  6. #
  7. # Open Source License Information:
  8. #
  9. # PBS Pro is free software. You can redistribute it and/or modify it under the
  10. # terms of the GNU Affero General Public License as published by the Free
  11. # Software Foundation, either version 3 of the License, or (at your option) any
  12. # later version.
  13. #
  14. # PBS Pro is distributed in the hope that it will be useful, but WITHOUT ANY
  15. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  16. # FOR A PARTICULAR PURPOSE.
  17. # See the GNU Affero General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU Affero General Public License
  20. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. #
  22. # Commercial License Information:
  23. #
  24. # For a copy of the commercial license terms and conditions,
  25. # go to: (http://www.pbspro.com/UserArea/agreement.html)
  26. # or contact the Altair Legal Department.
  27. #
  28. # Altair’s dual-license business model allows companies, individuals, and
  29. # organizations to create proprietary derivative works of PBS Pro and
  30. # distribute them - whether embedded or bundled with other software -
  31. # under a commercial license agreement.
  32. #
  33. # Use of Altair’s trademarks, including but not limited to "PBS™",
  34. # "PBS Professional®", and "PBS Pro™" and Altair’s logos is subject to Altair's
  35. # trademark licensing policies.
  36. import os
  37. import sys
  38. import time
  39. import logging
  40. import tempfile
  41. from stat import S_IWOTH
  42. from urlparse import urljoin
  43. from ptl.utils.pbs_dshutils import DshUtils
  44. from ptl.utils.pbs_cliutils import CliUtils
  45. try:
  46. from BeautifulSoup import BeautifulSoup
  47. except:
  48. pass
  49. class LcovUtils(object):
  50. """
  51. Coverage Utils
  52. :param cov_bin: Coverage binary
  53. :param html_bin: Coverage html binary
  54. :param cov_out: Coverage output directory
  55. :type cov_out: str or None
  56. :param data_dir: Coverage data directory
  57. :type data_dir: str or None
  58. :param html_nosrc: HTML reports without PBS source
  59. :type stml_nosrc: bool
  60. :param html_baseurl: HTML base url
  61. :type html_baseurl: str or None
  62. """
  63. du = DshUtils()
  64. logger = logging.getLogger(__name__)
  65. def __init__(self, cov_bin=None, html_bin=None, cov_out=None,
  66. data_dir=None, html_nosrc=False, html_baseurl=None):
  67. self.set_coverage_data_dir(data_dir)
  68. self.set_coverage_bin(cov_bin)
  69. self.set_genhtml_bin(html_bin)
  70. self.set_coverage_out(cov_out)
  71. self.set_html_nosource(html_nosrc)
  72. self.set_html_baseurl(html_baseurl)
  73. self.coverage_traces = []
  74. def set_html_baseurl(self, baseurl):
  75. """
  76. Set ``HTML`` base url
  77. """
  78. self.logger.info('coverage baseurl set to ' + str(baseurl))
  79. self.html_baseurl = baseurl
  80. def set_html_nosource(self, nosource=False):
  81. """
  82. Set HTML nosource parameter to False.
  83. """
  84. self.logger.info('coverage no-source set to ' + str(nosource))
  85. self.html_nosrc = nosource
  86. def set_coverage_bin(self, cov_bin=None):
  87. """
  88. Set the coverage binary
  89. :param cov_binary: Coverage bin to set
  90. """
  91. if cov_bin is None:
  92. cov_bin = 'lcov'
  93. rv = CliUtils.check_bin(cov_bin)
  94. if not rv:
  95. self.logger.error('None lcov_bin defined!')
  96. sys.exit(1)
  97. else:
  98. self.logger.info('coverage utility set to ' + cov_bin)
  99. self.cov_bin = cov_bin
  100. return rv
  101. def set_genhtml_bin(self, html_bin=None):
  102. """
  103. Set HTML generation utility.
  104. :param html_bin: HTML bin to set
  105. """
  106. if html_bin is None:
  107. html_bin = 'genhtml'
  108. rv = CliUtils.check_bin(html_bin)
  109. if not rv:
  110. self.logger.error('%s tool not found' % (html_bin))
  111. self.html_bin = None
  112. else:
  113. self.logger.info('HTML generation utility set to ' + html_bin)
  114. self.html_bin = html_bin
  115. return rv
  116. def set_coverage_out(self, cov_out=None):
  117. """
  118. Set the coverage output directory
  119. :param cov_out: Coverage output directory path.
  120. """
  121. if cov_out is None:
  122. d = 'pbscov-' + time.strftime('%Y%m%d_%H%M%S', time.localtime())
  123. cov_out = os.path.join(tempfile.gettempdir(), d)
  124. if not os.path.isdir(cov_out):
  125. os.mkdir(cov_out)
  126. self.logger.info('coverage output directory set to ' + cov_out)
  127. self.cov_out = cov_out
  128. def set_coverage_data_dir(self, data=None):
  129. """
  130. Set the coverage data directory
  131. :param data: Data directory path
  132. :returns: True if file name ends with .gcno else return False
  133. """
  134. self.data_dir = data
  135. if self.data_dir is not None:
  136. walker = os.walk(self.data_dir)
  137. for _, _, files in walker:
  138. for f in files:
  139. if f.endswith('.gcno'):
  140. return True
  141. return False
  142. def add_trace(self, trace):
  143. """
  144. Add coverage trace
  145. :param trace: Coverage trace
  146. """
  147. if trace not in self.coverage_traces:
  148. self.logger.info('Adding coverage trace: %s' % (trace))
  149. self.coverage_traces.append(trace)
  150. def create_coverage_data_files(self, path):
  151. """
  152. Create .gcda counterpart files for every .gcno file and give it
  153. read/write permissions
  154. """
  155. walker = os.walk(path)
  156. for root, _, files in walker:
  157. for f in files:
  158. if f.endswith('.gcda'):
  159. pf = os.path.join(root, f)
  160. s = os.stat(pf)
  161. if (s.st_mode & S_IWOTH) == 0:
  162. self.du.run_cmd(cmd=['chmod', '666', pf],
  163. level=logging.DEBUG, sudo=True)
  164. elif f.endswith('.gcno'):
  165. nf = f.replace('.gcno', '.gcda')
  166. pf = os.path.join(root, nf)
  167. if not os.path.isfile(pf):
  168. self.du.run_cmd(cmd=['touch', pf],
  169. level=logging.DEBUG, sudo=True)
  170. self.du.run_cmd(cmd=['chmod', '666', pf],
  171. level=logging.DEBUG, sudo=True)
  172. def initialize_coverage(self, out=None, name=None):
  173. """
  174. Initialize coverage
  175. :param out: Output path
  176. :type out: str or None
  177. :param name: name of the command
  178. :type name: str or None
  179. """
  180. if self.data_dir is not None:
  181. if out is None:
  182. out = os.path.join(self.cov_out, 'baseline.info')
  183. self.logger.info('Initializing coverage data to ' + out)
  184. self.create_coverage_data_files(self.data_dir)
  185. cmd = [self.cov_bin]
  186. if name is not None:
  187. cmd += ['-t', name]
  188. cmd += ['-i', '-d', self.data_dir, '-c', '-o', out]
  189. self.du.run_cmd(cmd=cmd, logerr=False)
  190. self.add_trace(out)
  191. def capture_coverage(self, out=None, name=None):
  192. """
  193. Capture the coverage parameters
  194. """
  195. if self.data_dir is not None:
  196. if out is None:
  197. out = os.path.join(self.cov_out, 'tests.info')
  198. self.logger.info('Capturing coverage data to ' + out)
  199. cmd = [self.cov_bin]
  200. if name is not None:
  201. cmd += ['-t', name]
  202. cmd += ['-c', '-d', self.data_dir, '-o', out]
  203. self.du.run_cmd(cmd=cmd, logerr=False)
  204. self.add_trace(out)
  205. def zero_coverage(self):
  206. """
  207. Zero the data counters. Note that a process would need to be restarted
  208. in order to collect data again, running ``--initialize`` will not get
  209. populate the data counters
  210. """
  211. if self.data_dir is not None:
  212. self.logger.info('Resetting coverage data')
  213. cmd = [self.cov_bin, '-z', '-d', self.data_dir]
  214. self.du.run_cmd(cmd=cmd, logerr=False)
  215. def merge_coverage_traces(self, out=None, name=None, exclude=None):
  216. """
  217. Merge the coverage traces
  218. """
  219. if not self.coverage_traces:
  220. return
  221. if out is None:
  222. out = os.path.join(self.cov_out, 'total.info')
  223. self.logger.info('Merging coverage traces to ' + out)
  224. if exclude is not None:
  225. tmpout = out + '.tmp'
  226. else:
  227. tmpout = out
  228. cmd = [self.cov_bin]
  229. if name is not None:
  230. cmd += ['-t', name]
  231. for t in self.coverage_traces:
  232. cmd += ['-a', t]
  233. cmd += ['-o', tmpout]
  234. self.du.run_cmd(cmd=cmd, logerr=False)
  235. if exclude is not None:
  236. cmd = [self.cov_bin]
  237. if name is not None:
  238. cmd += ['-t', name]
  239. cmd += ['-r', tmpout] + exclude + ['-o', out]
  240. self.du.run_cmd(cmd=cmd, logerr=False)
  241. self.du.rm(path=tmpout, logerr=False)
  242. def generate_html(self, out=None, html_out=None, html_nosrc=False):
  243. """
  244. Generate the ``HTML`` report
  245. """
  246. if self.html_bin is None:
  247. self.logger.warn('No genhtml bin is defined')
  248. return
  249. if out is None:
  250. out = os.path.join(self.cov_out, 'total.info')
  251. if not os.path.isfile(out):
  252. return
  253. if html_out is None:
  254. html_out = os.path.join(self.cov_out, 'html')
  255. if (self.html_nosrc or html_nosrc):
  256. self.logger.info('Generating HTML reports (without PBS source)'
  257. ' from coverage data')
  258. cmd = [self.html_bin, '--no-source', out]
  259. cmd += ['-o', html_out]
  260. self.du.run_cmd(cmd=cmd, logerr=False)
  261. else:
  262. self.logger.info('Generating HTML reports (with PBS Source) from'
  263. ' coverage data')
  264. cmd = [self.html_bin, out, '-o', html_out]
  265. self.du.run_cmd(cmd=cmd, logerr=False)
  266. def change_baseurl(self, html_out=None, html_baseurl=None):
  267. """
  268. Change the ``HTML`` base url
  269. """
  270. if html_baseurl is None:
  271. html_baseurl = self.html_baseurl
  272. if html_baseurl is None:
  273. return
  274. if html_out is None:
  275. html_out = os.path.join(self.cov_out, 'html')
  276. if not os.path.isdir(html_out):
  277. return
  278. html_out_bu = os.path.join(os.path.dirname(html_out),
  279. os.path.basename(html_out) + '_baseurl')
  280. if html_baseurl[-1] != '/':
  281. html_baseurl += '/'
  282. self.logger.info('Changing baseurl to %s' % (html_baseurl))
  283. self.du.run_copy(src=html_out, dest=html_out_bu, recursive=True)
  284. for root, _, files in os.walk(html_out_bu):
  285. newroot = root.split(html_out_bu)[1]
  286. if ((len(newroot) > 0) and (newroot[0] == '/')):
  287. newroot = newroot[1:]
  288. newroot = urljoin(html_baseurl, newroot)
  289. if newroot[-1] != '/':
  290. newroot += '/'
  291. print root, newroot
  292. for f in files:
  293. if not f.endswith('.html'):
  294. continue
  295. f = os.path.join(root, f)
  296. fd = open(f, 'r')
  297. line = ''.join(fd.readlines())
  298. fd.close()
  299. tree = BeautifulSoup(line)
  300. for a in tree.findAll('a'):
  301. href = a['href']
  302. if href.startswith('http://'):
  303. continue
  304. a['href'] = urljoin(newroot, href)
  305. for img in tree.findAll('img'):
  306. img['src'] = urljoin(newroot, img['src'])
  307. for css in tree.findAll('link', rel='stylesheet'):
  308. css['href'] = urljoin(newroot, css['href'])
  309. fd = open(f, 'w+')
  310. fd.write(str(tree))
  311. fd.close()
  312. def summarize_coverage(self, out=None):
  313. """
  314. Summarize the coverage output
  315. """
  316. if out is None:
  317. out = os.path.join(self.cov_out, 'total.info')
  318. if not os.path.isfile(out):
  319. return ''
  320. self.logger.info('Summarizing coverage data from ' + out)
  321. cmd = [self.cov_bin, '--summary', out]
  322. return self.du.run_cmd(cmd=cmd, logerr=False)['err']