1"""Activate coverage at python startup if appropriate.

2

3 The python site initialisation will ensure that anything we import

4 will be removed and not visible at the end of python startup. However

5 we minimise all work by putting these init actions in this separate

6 module and only importing what is needed when needed.

7

8 For normal python startup when coverage should not be activated the pth

9 file checks a single env var and does not import or call the init fn

10 here.

11

12 For python startup when an ancestor process has set the env indicating

13 that code coverage is being collected we activate coverage based on

14 info passed via env vars.

15"""

16 import atexit

17 import os

18 import signal

19

20_active_cov = None

21

22

23 def init():

24 # Only continue if ancestor process has set everything needed in

25 # the env.

26 global _active_cov

27

28 cov_source = os.environ.get('COV_CORE_SOURCE')

29 cov_config = os.environ.get('COV_CORE_CONFIG')

30 cov_datafile = os.environ.get('COV_CORE_DATAFILE')

31 cov_branch = True if os.environ.get('COV_CORE_BRANCH') == 'enabled' else None

32 cov_context = os.environ.get('COV_CORE_CONTEXT')

33

34 if cov_datafile:

35 if _active_cov:

36 cleanup()

37 # Import what we need to activate coverage.

38 import coverage

39

40 # Determine all source roots.

41 if cov_source in os.pathsep:

42 cov_source = None

43 else:

44 cov_source = cov_source.split(os.pathsep)

45 if cov_config == os.pathsep:

46 cov_config = True

47

48 # Activate coverage for this process.

49 cov = _active_cov = coverage.Coverage(

50 source=cov_source,

51 branch=cov_branch,

52 data_suffix=True,

53 config_file=cov_config,

54 auto_data=True,

55 data_file=cov_datafile

56 )

57 cov.load()

58 cov.start()

59 if cov_context:

60 cov.switch_context(cov_context)

61 cov._warn_no_data = False

62 cov._warn_unimported_source = False

63 return cov

64

65

66 def _cleanup(cov):

67 if cov is not None:

68 cov.stop()

69 cov.save()

70 cov._auto_save = False # prevent autosaving from cov._atexit in case the interpreter lacks atexit.unregister

71 try:

72 atexit.unregister(cov._atexit)

73 except Exception:

74 pass

75

76

77 def cleanup():

78 global _active_cov

79 global _cleanup_in_progress

80 global _pending_signal

81

82 _cleanup_in_progress = True

83 _cleanup(_active_cov)

84 _active_cov = None

85 _cleanup_in_progress = False

86 if _pending_signal:

87 pending_signal = _pending_signal

88 _pending_signal = None

89 _signal_cleanup_handler(*pending_signal)

90

91

92_previous_handlers = {}

93_pending_signal = None

94_cleanup_in_progress = False

95

96

97 def _signal_cleanup_handler(signum, frame):

98 global _pending_signal

99 if _cleanup_in_progress:

100 _pending_signal = signum, frame

101 return

102 cleanup()

103 _previous_handler = _previous_handlers.get(signum)

104 if _previous_handler == signal.SIG_IGN:

105 return

106 elif _previous_handler and _previous_handler is not _signal_cleanup_handler:

107 _previous_handler(signum, frame)

108 elif signum == signal.SIGTERM:

109 os._exit(128 + signum)

110 elif signum == signal.SIGINT:

111 raise KeyboardInterrupt()

112

113

114 def cleanup_on_signal(signum):

115 previous = signal.getsignal(signum)

116 if previous is not _signal_cleanup_handler:

117 _previous_handlers[signum] = previous

118 signal.signal(signum, _signal_cleanup_handler)

119

120

121 def cleanup_on_sigterm():

122 cleanup_on_signal(signal.SIGTERM)