process_jam_log.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. #!/usr/bin/python
  2. # Copyright 2008 Rene Rivera
  3. # Distributed under the Boost Software License, Version 1.0.
  4. # (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
  5. import re
  6. import optparse
  7. import time
  8. import xml.dom.minidom
  9. from xml.sax.saxutils import unescape, escape
  10. import os.path
  11. #~ Process a bjam XML log into the XML log format for Boost result processing.
  12. class BJamLog2Results:
  13. def __init__(self,args=None):
  14. opt = optparse.OptionParser(
  15. usage="%prog [options] input")
  16. opt.add_option( '--output',
  17. help="output file" )
  18. opt.add_option( '--runner',
  19. help="runner ID (e.g. 'Metacomm')" )
  20. opt.add_option( '--comment',
  21. help="an HTML comment file to be inserted in the reports" )
  22. opt.add_option( '--tag',
  23. help="the tag for the results" )
  24. opt.add_option( '--incremental',
  25. help="do incremental run (do not remove previous binaries)",
  26. action='store_true' )
  27. opt.add_option( '--platform' )
  28. opt.add_option( '--source' )
  29. opt.add_option( '--revision' )
  30. self.output = None
  31. self.runner = None
  32. self.comment='comment.html'
  33. self.tag='trunk'
  34. self.incremental=False
  35. self.platform=''
  36. self.source='SVN'
  37. self.revision=None
  38. self.input = []
  39. ( _opt_, self.input ) = opt.parse_args(args,self)
  40. self.results = xml.dom.minidom.parseString('''<?xml version="1.0" encoding="UTF-8"?>
  41. <test-run
  42. source="%(source)s"
  43. runner="%(runner)s"
  44. timestamp=""
  45. platform="%(platform)s"
  46. tag="%(tag)s"
  47. run-type="%(run-type)s"
  48. revision="%(revision)s">
  49. </test-run>
  50. ''' % {
  51. 'source' : self.source,
  52. 'runner' : self.runner,
  53. 'platform' : self.platform,
  54. 'tag' : self.tag,
  55. 'run-type' : 'incremental' if self.incremental else 'full',
  56. 'revision' : self.revision,
  57. } )
  58. self.test = {}
  59. self.target = {}
  60. self.parent = {}
  61. self.log = {}
  62. self.add_log()
  63. self.gen_output()
  64. #~ print self.test
  65. #~ print self.target
  66. def add_log(self):
  67. bjam_log = xml.dom.minidom.parse(self.input[0])
  68. self.x(bjam_log.documentElement)
  69. def gen_output(self):
  70. if self.output:
  71. out = open(self.output,'w')
  72. else:
  73. out = sys.stdout
  74. if out:
  75. self.results.writexml(out,encoding='utf-8')
  76. def tostring(self):
  77. return self.results.toxml('utf-8')
  78. def x(self, *context, **kwargs):
  79. node = None
  80. names = [ ]
  81. for c in context:
  82. if c:
  83. if not isinstance(c,xml.dom.Node):
  84. suffix = '_'+c.replace('-','_').replace('#','_')
  85. else:
  86. suffix = '_'+c.nodeName.replace('-','_').replace('#','_')
  87. node = c
  88. names.append('x')
  89. names = map(lambda x: x+suffix,names)
  90. if node:
  91. for name in names:
  92. if hasattr(self,name):
  93. return getattr(self,name)(node,**kwargs)
  94. else:
  95. assert False, 'Unknown node type %s'%(name)
  96. return None
  97. #~ The single top-level build element...
  98. def x_build( self, node ):
  99. test_run = self.results.documentElement
  100. #~ Iterate over the sub-sections in a specific order to build up the
  101. #~ cross-reference information and the XML output.
  102. for type in ('timestamp','comment','test','targets','action'):
  103. items = self.x(node,type)
  104. #~ Any items generated by the processing are inteserted into the results.
  105. if items:
  106. for item in items:
  107. if item:
  108. test_run.appendChild(self.results.createTextNode("\n"))
  109. test_run.appendChild(item)
  110. return None
  111. #~ The timestamp goes to the corresponding attribute in the result.
  112. def x_build_timestamp( self, node ):
  113. test_run = self.results.documentElement
  114. timestamp = self.get_child(self.get_child(node,tag='timestamp'),tag='#cdata-section').data.strip()
  115. test_run.setAttribute('timestamp',timestamp)
  116. return None
  117. #~ Comment file becomes a comment node.
  118. def x_build_comment( self, node ):
  119. comment = None
  120. if self.comment:
  121. comment_f = open(self.comment)
  122. if comment_f:
  123. comment = comment_f.read()
  124. comment_f.close()
  125. if not comment:
  126. comment = ''
  127. return [self.new_text('comment',comment)]
  128. #~ Tests are remembered for future reference.
  129. def x_build_test( self, node ):
  130. test_run = self.results.documentElement
  131. test_node = self.get_child(node,tag='test')
  132. while test_node:
  133. test_name = test_node.getAttribute('name')
  134. self.test[test_name] = {
  135. 'library' : test_name.split('/',1)[0],
  136. 'test-name' : test_name.split('/',1)[1],
  137. 'test-type' : test_node.getAttribute('type').lower(),
  138. 'test-program' : self.get_child_data(test_node,tag='source').strip(),
  139. 'target' : self.get_child_data(test_node,tag='target').strip(),
  140. 'info' : self.get_child_data(test_node,tag='info',strip=True)
  141. }
  142. #~ Add a lookup for the test given the test target.
  143. self.target[self.test[test_name]['target']] = test_name
  144. test_node = self.get_sibling(test_node.nextSibling,tag='test')
  145. return None
  146. #~ Process the target dependency DAG into an ancestry tree so we can look up
  147. #~ which top-level library and test targets specific build actions correspond to.
  148. def x_build_targets( self, node ):
  149. test_run = self.results.documentElement
  150. target_node = self.get_child(self.get_child(node,tag='targets'),tag='target')
  151. while target_node:
  152. name = self.get_child_data(target_node,tag='name').strip()
  153. path = self.get_child_data(target_node,tag='path').strip()
  154. jam_target = self.get_child_data(target_node,tag='jam-target').strip()
  155. #~ Map for jam targets to virtual targets.
  156. self.target[jam_target] = {
  157. 'name' : name,
  158. 'path' : path
  159. }
  160. #~ Create the ancestry.
  161. dep_node = self.get_child(self.get_child(target_node,tag='dependencies'),tag='dependency')
  162. while dep_node:
  163. child = self.get_data(dep_node).strip()
  164. child_jam_target = '<p%s>%s' % (path,child.split('//',1)[1])
  165. self.parent[child_jam_target] = jam_target
  166. #~ print "--- %s\n ^ %s" %(jam_target,child_jam_target)
  167. dep_node = self.get_sibling(dep_node.nextSibling,tag='dependency')
  168. target_node = self.get_sibling(target_node.nextSibling,tag='target')
  169. return None
  170. #~ Given a build action log, process into the corresponding test log and
  171. #~ specific test log sub-part.
  172. def x_build_action( self, node ):
  173. test_run = self.results.documentElement
  174. action_node = self.get_child(node,tag='action')
  175. while action_node:
  176. name = self.get_child(action_node,tag='name')
  177. if name:
  178. name = self.get_data(name)
  179. #~ Based on the action, we decide what sub-section the log
  180. #~ should go into.
  181. action_type = None
  182. if re.match('[^%]+%[^.]+[.](compile)',name):
  183. action_type = 'compile'
  184. elif re.match('[^%]+%[^.]+[.](link|archive)',name):
  185. action_type = 'link'
  186. elif re.match('[^%]+%testing[.](capture-output)',name):
  187. action_type = 'run'
  188. elif re.match('[^%]+%testing[.](expect-failure|expect-success)',name):
  189. action_type = 'result'
  190. #~ print "+ [%s] %s %s :: %s" %(action_type,name,'','')
  191. if action_type:
  192. #~ Get the corresponding test.
  193. (target,test) = self.get_test(action_node,type=action_type)
  194. #~ And the log node, which we will add the results to.
  195. log = self.get_log(action_node,test)
  196. #~ print "--- [%s] %s %s :: %s" %(action_type,name,target,test)
  197. #~ Collect some basic info about the action.
  198. result_data = "%(info)s\n\n%(command)s\n%(output)s\n" % {
  199. 'command' : self.get_action_command(action_node,action_type),
  200. 'output' : self.get_action_output(action_node,action_type),
  201. 'info' : self.get_action_info(action_node,action_type)
  202. }
  203. #~ For the test result status we find the appropriate node
  204. #~ based on the type of test. Then adjust the result status
  205. #~ acorrdingly. This makes the result status reflect the
  206. #~ expectation as the result pages post processing does not
  207. #~ account for this inversion.
  208. action_tag = action_type
  209. if action_type == 'result':
  210. if re.match(r'^compile',test['test-type']):
  211. action_tag = 'compile'
  212. elif re.match(r'^link',test['test-type']):
  213. action_tag = 'link'
  214. elif re.match(r'^run',test['test-type']):
  215. action_tag = 'run'
  216. #~ The result sub-part we will add this result to.
  217. result_node = self.get_child(log,tag=action_tag)
  218. if not result_node:
  219. #~ If we don't have one already, create it and add the result.
  220. result_node = self.new_text(action_tag,result_data,
  221. result='succeed' if action_node.getAttribute('status') == '0' else 'fail',
  222. timestamp=action_node.getAttribute('start'))
  223. log.appendChild(self.results.createTextNode("\n"))
  224. log.appendChild(result_node)
  225. else:
  226. #~ For an existing result node we set the status to fail
  227. #~ when any of the individual actions fail, except for result
  228. #~ status.
  229. if action_type != 'result':
  230. result = result_node.getAttribute('result')
  231. if action_node.getAttribute('status') != '0':
  232. result = 'fail'
  233. else:
  234. result = 'succeed' if action_node.getAttribute('status') == '0' else 'fail'
  235. result_node.setAttribute('result',result)
  236. result_node.appendChild(self.results.createTextNode("\n"))
  237. result_node.appendChild(self.results.createTextNode(result_data))
  238. action_node = self.get_sibling(action_node.nextSibling,tag='action')
  239. return self.log.values()
  240. #~ The command executed for the action. For run actions we omit the command
  241. #~ as it's just noise.
  242. def get_action_command( self, action_node, action_type ):
  243. if action_type != 'run':
  244. return self.get_child_data(action_node,tag='command')
  245. else:
  246. return ''
  247. #~ The command output.
  248. def get_action_output( self, action_node, action_type ):
  249. return self.get_child_data(action_node,tag='output',default='')
  250. #~ Some basic info about the action.
  251. def get_action_info( self, action_node, action_type ):
  252. info = ""
  253. #~ The jam action and target.
  254. info += "%s %s\n" %(self.get_child_data(action_node,tag='name'),
  255. self.get_child_data(action_node,tag='path'))
  256. #~ The timing of the action.
  257. info += "Time: (start) %s -- (end) %s -- (user) %s -- (system) %s\n" %(
  258. action_node.getAttribute('start'), action_node.getAttribute('end'),
  259. action_node.getAttribute('user'), action_node.getAttribute('system'))
  260. #~ And for compiles some context that may be hidden if using response files.
  261. if action_type == 'compile':
  262. define = self.get_child(self.get_child(action_node,tag='properties'),name='define')
  263. while define:
  264. info += "Define: %s\n" %(self.get_data(define,strip=True))
  265. define = self.get_sibling(define.nextSibling,name='define')
  266. return info
  267. #~ Find the test corresponding to an action. For testing targets these
  268. #~ are the ones pre-declared in the --dump-test option. For libraries
  269. #~ we create a dummy test as needed.
  270. def get_test( self, node, type = None ):
  271. target = self.get_child_data(node,tag='jam-target')
  272. base = self.target[target]['name']
  273. while target in self.parent:
  274. target = self.parent[target]
  275. #~ main-target-type is a precise indicator of what the build target is
  276. #~ proginally meant to be.
  277. main_type = self.get_child_data(self.get_child(node,tag='properties'),
  278. name='main-target-type',strip=True)
  279. if main_type == 'LIB' and type:
  280. lib = self.target[target]['name']
  281. if not lib in self.test:
  282. self.test[lib] = {
  283. 'library' : re.search(r'libs/([^/]+)',lib).group(1),
  284. 'test-name' : os.path.basename(lib),
  285. 'test-type' : 'lib',
  286. 'test-program' : os.path.basename(lib),
  287. 'target' : lib
  288. }
  289. test = self.test[lib]
  290. else:
  291. test = self.test[self.target[self.target[target]['name']]]
  292. return (base,test)
  293. #~ Find, or create, the test-log node to add results to.
  294. def get_log( self, node, test ):
  295. target_directory = os.path.dirname(self.get_child_data(
  296. node,tag='path',strip=True))
  297. target_directory = re.sub(r'.*[/\\]bin[.]v2[/\\]','',target_directory)
  298. target_directory = re.sub(r'[\\]','/',target_directory)
  299. if not target_directory in self.log:
  300. self.log[target_directory] = self.new_node('test-log',
  301. library=test['library'],
  302. test_name=test['test-name'],
  303. test_type=test['test-type'],
  304. test_program=test['test-program'],
  305. toolset=self.get_toolset(node),
  306. target_directory=target_directory,
  307. show_run_output='true' if 'info' in test and test['info'] == 'always_show_run_output' else 'false')
  308. return self.log[target_directory]
  309. #~ The precise toolset from the build properties.
  310. def get_toolset( self, node ):
  311. toolset = self.get_child_data(self.get_child(node,tag='properties'),
  312. name='toolset',strip=True)
  313. toolset_version = self.get_child_data(self.get_child(node,tag='properties'),
  314. name='toolset-%s:version'%toolset,strip=True)
  315. return '%s-%s' %(toolset,toolset_version)
  316. #~ XML utilities...
  317. def get_sibling( self, sibling, tag = None, id = None, name = None ):
  318. n = sibling
  319. while n:
  320. found = True
  321. if tag and found:
  322. found = found and tag == n.nodeName
  323. if (id or name) and found:
  324. found = found and n.nodeType == xml.dom.Node.ELEMENT_NODE
  325. if id and found:
  326. if n.hasAttribute('id'):
  327. found = found and n.getAttribute('id') == id
  328. else:
  329. found = found and n.hasAttribute('id') and n.getAttribute('id') == id
  330. if name and found:
  331. found = found and n.hasAttribute('name') and n.getAttribute('name') == name
  332. if found:
  333. return n
  334. n = n.nextSibling
  335. return None
  336. def get_child( self, root, tag = None, id = None, name = None ):
  337. return self.get_sibling(root.firstChild,tag=tag,id=id,name=name)
  338. def get_data( self, node, strip = False, default = None ):
  339. data = None
  340. if node:
  341. if not data:
  342. data = self.get_child(node,tag='#text')
  343. if not data:
  344. data = self.get_child(node,tag='#cdata-section')
  345. if data:
  346. data = data.data if not strip else data.data.strip()
  347. if not data:
  348. data = default
  349. return data
  350. def get_child_data( self, root, tag = None, id = None, name = None, strip = False, default = None ):
  351. return self.get_data(self.get_child(root,tag=tag,id=id,name=name),strip=strip,default=default)
  352. def new_node( self, tag, *child, **kwargs ):
  353. result = self.results.createElement(tag)
  354. for k in kwargs.keys():
  355. if kwargs[k] != '':
  356. if k == 'id':
  357. result.setAttribute('id',kwargs[k])
  358. elif k == 'klass':
  359. result.setAttribute('class',kwargs[k])
  360. else:
  361. result.setAttribute(k.replace('_','-'),kwargs[k])
  362. for c in child:
  363. if c:
  364. result.appendChild(c)
  365. return result
  366. def new_text( self, tag, data, **kwargs ):
  367. result = self.new_node(tag,**kwargs)
  368. data = data.strip()
  369. if len(data) > 0:
  370. result.appendChild(self.results.createTextNode(data))
  371. return result
  372. if __name__ == '__main__': BJamLog2Results()
粤ICP备19079148号