process_jam_log.py 19 KB

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