coverage.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env python3
  2. # Contest Management System - http://cms-dev.github.io/
  3. # Copyright © 2012 Bernard Blackham <bernard@largestprime.net>
  4. # Copyright © 2013-2018 Stefano Maggiolo <s.maggiolo@gmail.com>
  5. # Copyright © 2013-2016 Luca Wehrstedt <luca.wehrstedt@gmail.com>
  6. # Copyright © 2014 Luca Versari <veluca93@gmail.com>
  7. # Copyright © 2014 William Di Luigi <williamdiluigi@gmail.com>
  8. # Copyright © 2016 Peyman Jabbarzade Ganje <peyman.jabarzade@gmail.com>
  9. # Copyright © 2017 Luca Chiodini <luca@chiodini.org>
  10. # Copyright © 2021 Andrey Vihrov <andrey.vihrov@gmail.com>
  11. #
  12. # This program is free software: you can redistribute it and/or modify
  13. # it under the terms of the GNU Affero General Public License as
  14. # published by the Free Software Foundation, either version 3 of the
  15. # License, or (at your option) any later version.
  16. #
  17. # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. # GNU Affero General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU Affero General Public License
  23. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. import os
  25. import logging
  26. import requests
  27. import subprocess
  28. import sys
  29. from urllib.parse import urlparse
  30. from cmstestsuite import CONFIG, sh
  31. logger = logging.getLogger(__name__)
  32. _COVERAGE_DIRECTORIES = [
  33. "cms",
  34. "cmscommon",
  35. "cmscontrib",
  36. "cmsranking",
  37. "cmstaskenv",
  38. ]
  39. _COVERAGE_CMDLINE = [
  40. sys.executable, "-m", "coverage", "run", "-p",
  41. "--source=%s" % ",".join(_COVERAGE_DIRECTORIES)]
  42. def coverage_cmdline(cmdline):
  43. """Return a cmdline possibly decorated to record coverage."""
  44. if CONFIG.get('COVERAGE', False):
  45. return _COVERAGE_CMDLINE + cmdline
  46. else:
  47. return cmdline
  48. def clear_coverage():
  49. """Clear existing coverage reports."""
  50. if CONFIG.get('COVERAGE', False):
  51. logging.info("Clearing old coverage data.")
  52. sh([sys.executable, "-m", "coverage", "erase"])
  53. def combine_coverage():
  54. """Combine coverage reports from different programs."""
  55. if CONFIG.get('COVERAGE', False):
  56. logger.info("Combining coverage results.")
  57. sh([sys.executable, "-m", "coverage", "combine"])
  58. sh([sys.executable, "-m", "coverage", "xml"])
  59. # Cache directory for subsequent runs.
  60. _CODECOV_DIR = os.path.join("cache", "cmstestsuite", "codecov")
  61. def _download_file(url, out):
  62. """Download and save a binary file.
  63. url (str): file to download.
  64. out (str): output file name.
  65. """
  66. r = requests.get(url, stream=True)
  67. r.raise_for_status()
  68. with open(out, "wb") as f:
  69. for chunk in r.iter_content(chunk_size=4096):
  70. f.write(chunk)
  71. def _import_pgp_key(gpg_home, keyring, fingerprint):
  72. """Import a PGP key from public keyservers.
  73. gpg_home (str): GnuPG home directory.
  74. keyring (str): Keyring file to use.
  75. fingerprint (str): PGP key fingerprint.
  76. """
  77. keyservers = [ "hkps://keyserver.ubuntu.com", "hkps://pgp.mit.edu" ]
  78. for keyserver in keyservers:
  79. logger.info("Importing PGP key %s from %s." %
  80. (fingerprint[-8:], urlparse(keyserver).netloc))
  81. try:
  82. subprocess.check_call(["gpg", "--homedir", gpg_home, "--keyring",
  83. keyring, "--no-default-keyring",
  84. "--keyserver", keyserver,
  85. "--recv-keys", fingerprint])
  86. return
  87. except subprocess.CalledProcessError:
  88. logger.warning("PGP key import failed.", exc_info=True)
  89. raise Exception("No usable keyservers left.")
  90. def _get_codecov_uploader():
  91. """Fetch and return the Codecov uploader.
  92. return (str): path to the stored uploader.
  93. """
  94. base_url = "https://uploader.codecov.io/latest/linux/"
  95. executable = "codecov"
  96. shasum = "codecov.SHA256SUM"
  97. sigfile = "codecov.SHA256SUM.sig"
  98. gpg_home = os.path.realpath(os.path.join(_CODECOV_DIR, "gnupg"))
  99. # Codecov Uploader (Codecov Uploader Verification Key)
  100. # <security@codecov.io>
  101. fingerprint = "27034E7FDB850E0BBC2C62FF806BB28AED779869"
  102. if not os.access(os.path.join(_CODECOV_DIR, executable), os.X_OK):
  103. os.makedirs(gpg_home, mode=0o700)
  104. _import_pgp_key(gpg_home, "trustedkeys.gpg", fingerprint)
  105. logger.info("Fetching Codecov uploader.")
  106. for name in [executable, shasum, sigfile]:
  107. _download_file(base_url + name, os.path.join(_CODECOV_DIR, name))
  108. logger.info("Checking Codecov uploader integrity.")
  109. subprocess.check_call(["gpgv", "--homedir", gpg_home, sigfile, shasum],
  110. cwd=_CODECOV_DIR)
  111. subprocess.check_call(["sha256sum", "-c", shasum], cwd=_CODECOV_DIR)
  112. os.chmod(os.path.join(_CODECOV_DIR, executable), 0o755)
  113. return os.path.join(_CODECOV_DIR, executable)
  114. def send_coverage_to_codecov(flag):
  115. """Send the coverage report to Codecov with the given flag."""
  116. if CONFIG.get('COVERAGE', False):
  117. logger.info("Sending coverage results to codecov for flag %s." % flag)
  118. uploader = _get_codecov_uploader()
  119. subprocess.check_call([uploader, "-F", flag])