coverage.py 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. # Copyright 2017 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. from mesonbuild import environment
  12. import argparse, sys, os, subprocess, pathlib
  13. def coverage(outputs, source_root, subproject_root, build_root, log_dir):
  14. outfiles = []
  15. exitcode = 0
  16. (gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe) = environment.find_coverage_tools()
  17. # gcovr >= 3.1 interprets rootdir differently
  18. if gcovr_new_rootdir:
  19. gcovr_rootdir = build_root
  20. else:
  21. gcovr_rootdir = source_root
  22. if not outputs or 'xml' in outputs:
  23. if gcovr_exe:
  24. subprocess.check_call([gcovr_exe,
  25. '-x',
  26. '-r', gcovr_rootdir,
  27. '-e', subproject_root,
  28. '-o', os.path.join(log_dir, 'coverage.xml'),
  29. ])
  30. outfiles.append(('Xml', pathlib.Path(log_dir, 'coverage.xml')))
  31. elif outputs:
  32. print('gcovr needed to generate Xml coverage report')
  33. exitcode = 1
  34. if not outputs or 'text' in outputs:
  35. if gcovr_exe:
  36. subprocess.check_call([gcovr_exe,
  37. '-r', gcovr_rootdir,
  38. '-e', subproject_root,
  39. '-o', os.path.join(log_dir, 'coverage.txt'),
  40. ])
  41. outfiles.append(('Text', pathlib.Path(log_dir, 'coverage.txt')))
  42. elif outputs:
  43. print('gcovr needed to generate text coverage report')
  44. exitcode = 1
  45. if not outputs or 'html' in outputs:
  46. if lcov_exe and genhtml_exe:
  47. htmloutdir = os.path.join(log_dir, 'coveragereport')
  48. covinfo = os.path.join(log_dir, 'coverage.info')
  49. initial_tracefile = covinfo + '.initial'
  50. run_tracefile = covinfo + '.run'
  51. raw_tracefile = covinfo + '.raw'
  52. subprocess.check_call([lcov_exe,
  53. '--directory', build_root,
  54. '--capture',
  55. '--initial',
  56. '--output-file',
  57. initial_tracefile])
  58. subprocess.check_call([lcov_exe,
  59. '--directory', build_root,
  60. '--capture',
  61. '--output-file', run_tracefile,
  62. '--no-checksum',
  63. '--rc', 'lcov_branch_coverage=1',
  64. ])
  65. # Join initial and test results.
  66. subprocess.check_call([lcov_exe,
  67. '-a', initial_tracefile,
  68. '-a', run_tracefile,
  69. '--rc', 'lcov_branch_coverage=1',
  70. '-o', raw_tracefile])
  71. # Remove all directories outside the source_root from the covinfo
  72. subprocess.check_call([lcov_exe,
  73. '--extract', raw_tracefile,
  74. os.path.join(source_root, '*'),
  75. '--rc', 'lcov_branch_coverage=1',
  76. '--output-file', covinfo])
  77. # Remove all directories inside subproject dir
  78. subprocess.check_call([lcov_exe,
  79. '--remove', covinfo,
  80. os.path.join(subproject_root, '*'),
  81. '--rc', 'lcov_branch_coverage=1',
  82. '--output-file', covinfo])
  83. subprocess.check_call([genhtml_exe,
  84. '--prefix', build_root,
  85. '--prefix', source_root,
  86. '--output-directory', htmloutdir,
  87. '--title', 'Code coverage',
  88. '--legend',
  89. '--show-details',
  90. '--branch-coverage',
  91. covinfo])
  92. outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
  93. elif gcovr_exe and gcovr_new_rootdir:
  94. htmloutdir = os.path.join(log_dir, 'coveragereport')
  95. if not os.path.isdir(htmloutdir):
  96. os.mkdir(htmloutdir)
  97. subprocess.check_call([gcovr_exe,
  98. '--html',
  99. '--html-details',
  100. '--print-summary',
  101. '-r', build_root,
  102. '-e', subproject_root,
  103. '-o', os.path.join(htmloutdir, 'index.html'),
  104. ])
  105. outfiles.append(('Html', pathlib.Path(htmloutdir, 'index.html')))
  106. elif outputs:
  107. print('lcov/genhtml or gcovr >= 3.2 needed to generate Html coverage report')
  108. exitcode = 1
  109. if not outputs and not outfiles:
  110. print('Need gcovr or lcov/genhtml to generate any coverage reports')
  111. exitcode = 1
  112. if outfiles:
  113. print('')
  114. for (filetype, path) in outfiles:
  115. print(filetype + ' coverage report can be found at', path.as_uri())
  116. return exitcode
  117. def run(args):
  118. if not os.path.isfile('build.ninja'):
  119. print('Coverage currently only works with the Ninja backend.')
  120. return 1
  121. parser = argparse.ArgumentParser(description='Generate coverage reports')
  122. parser.add_argument('--text', dest='outputs', action='append_const',
  123. const='text', help='generate Text report')
  124. parser.add_argument('--xml', dest='outputs', action='append_const',
  125. const='xml', help='generate Xml report')
  126. parser.add_argument('--html', dest='outputs', action='append_const',
  127. const='html', help='generate Html report')
  128. parser.add_argument('source_root')
  129. parser.add_argument('subproject_root')
  130. parser.add_argument('build_root')
  131. parser.add_argument('log_dir')
  132. options = parser.parse_args(args)
  133. return coverage(options.outputs, options.source_root,
  134. options.subproject_root, options.build_root,
  135. options.log_dir)
  136. if __name__ == '__main__':
  137. sys.exit(run(sys.argv[1:]))