You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

merge_coveralls.py 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #!/usr/bin/env python3
  2. from __future__ import print_function
  3. import argparse
  4. import json
  5. import os
  6. import sys
  7. import codecs
  8. import requests
  9. # Python 2/3 compatibility
  10. if sys.version_info.major > 2:
  11. xrange = range
  12. # install path to repository mapping
  13. # if path mapped to None, it means that the file should be ignored (i.e. test file/helper)
  14. # first matched path counts.
  15. # terminating slash should be added for directories
  16. path_mapping = [
  17. ("${install-dir}/share/rspamd/lib/fun.lua", None),
  18. ("${install-dir}/share/rspamd/lib/", "lualib/"),
  19. ("${install-dir}/share/rspamd/rules/" , "rules/"),
  20. ("${install-dir}/share/rspamd/lib/torch/" , None),
  21. ("${build-dir}/CMakeFiles/", None),
  22. ("${build-dir}/contrib/", None),
  23. ("${build-dir}/test", None),
  24. ("${project-root}/test/lua/", None),
  25. ("${project-root}/test/", None),
  26. ("${project-root}/clang-plugin/", None),
  27. ("${project-root}/CMakeFiles/", None),
  28. ("${project-root}/contrib/", None),
  29. ("${project-root}/", ""),
  30. ("contrib/", None),
  31. ("CMakeFiles/", None),
  32. ]
  33. parser = argparse.ArgumentParser(description='')
  34. parser.add_argument('--input', required=True, nargs='+', help='input files')
  35. parser.add_argument('--output', help='output file)')
  36. parser.add_argument('--root', default="/rspamd/src/github.com/rspamd/rspamd", help='repository root)')
  37. parser.add_argument('--install-dir', default="/rspamd/install", help='install root)')
  38. parser.add_argument('--build-dir', default="/rspamd/build", help='build root)')
  39. parser.add_argument('--token', help='If present, the file will be uploaded to coveralls)')
  40. def merge_coverage_vectors(c1, c2):
  41. assert(len(c1) == len(c2))
  42. for i in range(0, len(c1)):
  43. if c1[i] is None and c2[i] is None:
  44. pass
  45. elif type(c1[i]) is int and c2[i] is None:
  46. pass
  47. elif c1[i] is None and type(c2[i]) is int:
  48. c1[i] = c2[i]
  49. elif type(c1[i]) is int and type(c2[i]) is int:
  50. c1[i] += c2[i]
  51. else:
  52. raise RuntimeError("bad element types at %d: %s, %s", i, type(c1[i]), type(c1[i]))
  53. return c1
  54. def normalize_name(name):
  55. name = os.path.normpath(name)
  56. if not os.path.isabs(name):
  57. name = os.path.abspath(repository_root + "/" + name)
  58. for k in path_mapping:
  59. if name.startswith(k[0]):
  60. if k[1] is None:
  61. return None
  62. else:
  63. name = k[1] + name[len(k[0]):]
  64. break
  65. return name
  66. def merge(files, j1):
  67. for sf in j1['source_files']:
  68. name = normalize_name(sf['name'])
  69. if name is None:
  70. continue
  71. if name in files:
  72. files[name]['coverage'] = merge_coverage_vectors(files[name]['coverage'], sf['coverage'])
  73. else:
  74. sf['name'] = name
  75. files[name] = sf
  76. return files
  77. def prepare_path_mapping():
  78. for i in range(0, len(path_mapping)):
  79. new_key = path_mapping[i][0].replace("${install-dir}", install_dir)
  80. new_key = new_key.replace("${project-root}", repository_root)
  81. new_key = new_key.replace("${build-dir}", build_dir)
  82. path_mapping[i] = (new_key, path_mapping[i][1])
  83. if __name__ == '__main__':
  84. args = parser.parse_args()
  85. repository_root = os.path.abspath(os.path.expanduser(args.root))
  86. install_dir = os.path.normpath(os.path.expanduser(args.install_dir))
  87. build_dir = os.path.normpath(os.path.expanduser(args.build_dir))
  88. prepare_path_mapping()
  89. with codecs.open(args.input[0], 'r', encoding='utf-8') as fh:
  90. j1 = json.load(fh)
  91. files = merge({}, j1)
  92. for i in range(1, len(args.input)):
  93. with codecs.open(args.input[i], 'r', encoding='utf-8') as fh:
  94. j2 = json.load(fh)
  95. files = merge(files, j2)
  96. if 'git' not in j1 and 'git' in j2:
  97. j1['git'] = j2['git']
  98. if 'service_name' not in j1 and 'service_name' in j2:
  99. j1['service_name'] = j2['service_name']
  100. if 'service_job_id' not in j1 and 'service_job_id' in j2:
  101. j1['service_job_id'] = j2['service_job_id']
  102. if os.getenv('CIRCLECI'):
  103. j1['service_name'] = 'circleci'
  104. j1['service_job_id'] = os.getenv('CIRCLE_BUILD_NUM')
  105. elif os.getenv('DRONE') == 'true':
  106. j1['service_name'] = 'drone'
  107. j1['service_branch'] = os.getenv('DRONE_COMMIT_BRANCH')
  108. j1['service_build_url'] = os.getenv('DRONE_BUILD_LINK')
  109. j1['service_number'] = os.getenv('DRONE_BUILD_NUMBER')
  110. j1['commit_sha'] = os.getenv('DRONE_COMMIT_SHA')
  111. if os.getenv('DRONE_BUILD_EVENT') == 'pull_request':
  112. j1['service_pull_request'] = os.getenv('DRONE_PULL_REQUEST')
  113. # git data can be filled by cpp-coveralls, but in our layout it can't find repo
  114. # so we can override git info witout merging
  115. j1['git'] = {
  116. 'head': {
  117. 'id': j1['commit_sha'],
  118. 'author_email': os.getenv('DRONE_COMMIT_AUTHOR_EMAIL'),
  119. 'message': os.getenv('DRONE_COMMIT_MESSAGE')
  120. },
  121. 'branch': j1['service_branch'],
  122. 'remotes': [{
  123. 'name': 'origin',
  124. 'url': os.getenv('DRONE_GIT_HTTP_URL')
  125. }]
  126. }
  127. j1['source_files'] = list(files.values())
  128. if args.output:
  129. with open(args.output, 'w') as f:
  130. f.write(json.dumps(j1))
  131. if args.token:
  132. j1['repo_token'] = args.token
  133. try:
  134. r = requests.post('https://coveralls.io/api/v1/jobs', files={"json_file": json.dumps(j1)})
  135. r.raise_for_status()
  136. except requests.exceptions.RequestException as e:
  137. print("Failed to send data to coveralls: %s" % e)
  138. sys.exit()
  139. try:
  140. response = r.json()
  141. print("[coveralls] %s" % response['message'])
  142. if 'url' in response:
  143. print("[coveralls] Uploaded to %s" % response['url'])
  144. except json.decoder.JSONDecodeError:
  145. print("Bad resonse: '%s'" % r.text)