Coverage for /Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm/teamcity/messages.py: 25%
153 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-06 12:04 +0200
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-06 12:04 +0200
1# coding=utf-8
2import errno
3from functools import wraps
4import sys
5import time
8if sys.version_info < (3, ):
9 # Python 2
10 # flake8: noqa
11 text_type = unicode
12else:
13 # Python 3
14 text_type = str
16# Capture some time functions to allow monkeypatching them in tests
17_time = time.time
18_localtime = time.localtime
19_strftime = time.strftime
21_quote = {"'": "|'", "|": "||", "\n": "|n", "\r": "|r", '[': '|[', ']': '|]'}
24def escape_value(value):
25 return "".join(_quote.get(x, x) for x in value)
28def retry_on_EAGAIN(callable):
29 # self.output seems to be non-blocking when running under teamcity.
30 @wraps(callable)
31 def wrapped(*args, **kwargs):
32 start_time = _time()
33 while True:
34 try:
35 return callable(*args, **kwargs)
36 except IOError as e:
37 if e.errno != errno.EAGAIN:
38 raise
39 # Give up after a minute.
40 if _time() - start_time > 60:
41 raise
42 time.sleep(.1)
43 return wrapped
46class TeamcityServiceMessages(object):
47 def __init__(self, output=None, now=_time, encoding='auto'):
48 if output is None: 48 ↛ 50line 48 didn't jump to line 50 because the condition on line 48 was always true
49 output = sys.stdout
50 if sys.version_info < (3, ) or not hasattr(output, 'buffer'): 50 ↛ 51line 50 didn't jump to line 51 because the condition on line 50 was never true
51 self.output = output
52 else:
53 self.output = output.buffer
54 self.now = now
56 if encoding and encoding != 'auto': 56 ↛ 57line 56 didn't jump to line 57 because the condition on line 56 was never true
57 self.encoding = encoding
58 elif getattr(output, 'encoding', None) or encoding == 'auto': 58 ↛ 64line 58 didn't jump to line 64 because the condition on line 58 was always true
59 # Default encoding to 'utf-8' because it sucks if we fail with a
60 # `UnicodeEncodeError` simply because LANG didn't get propagated to
61 # a subprocess or something and sys.stdout.encoding is None
62 self.encoding = getattr(output, 'encoding', None) or 'utf-8'
63 else:
64 self.encoding = None
66 def encode(self, value):
67 if self.encoding and isinstance(value, text_type): 67 ↛ 69line 67 didn't jump to line 69 because the condition on line 67 was always true
68 value = value.encode(self.encoding)
69 return value
71 def decode(self, value):
72 if self.encoding and not isinstance(value, text_type): 72 ↛ 73line 72 didn't jump to line 73 because the condition on line 72 was never true
73 value = value.decode(self.encoding)
74 return value
76 if sys.version_info < (3, ):
77 def escapeValue(self, value):
78 return escape_value(self.encode(value))
79 else:
80 def escapeValue(self, value):
81 return escape_value(self.decode(value))
83 def message(self, messageName, **properties):
84 current_time = self.now()
85 (current_time_int, current_time_fraction) = divmod(current_time, 1)
86 current_time_struct = _localtime(current_time_int)
88 timestamp = _strftime("%Y-%m-%dT%H:%M:%S.", current_time_struct) + "%03d" % (int(current_time_fraction * 1000))
89 message = ("##teamcity[%s timestamp='%s'" % (messageName, timestamp))
91 for k in sorted(properties.keys()):
92 value = properties[k]
93 if value is None:
94 continue
96 message += (" %s='%s'" % (k, self.escapeValue(value)))
98 message += ("]\n")
100 # Python may buffer it for a long time, flushing helps to see real-time result
101 retry_on_EAGAIN(self.output.write)(self.encode(message))
102 retry_on_EAGAIN(self.output.flush)()
104 def _single_value_message(self, messageName, value):
105 message = ("##teamcity[%s '%s']\n" % (messageName, self.escapeValue(value)))
107 # Python may buffer it for a long time, flushing helps to see real-time result
108 retry_on_EAGAIN(self.output.write)(self.encode(message))
109 retry_on_EAGAIN(self.output.flush)()
111 def blockOpened(self, name, flowId=None):
112 self.message('blockOpened', name=name, flowId=flowId)
114 def blockClosed(self, name, flowId=None):
115 self.message('blockClosed', name=name, flowId=flowId)
117 # Special PyCharm-specific extension to track subtests, additional property is ignored by TeamCity
118 def subTestBlockOpened(self, name, subTestResult, flowId=None):
119 self.message('blockOpened', name=name, subTestResult=subTestResult, flowId=flowId)
121 def block(self, name, flowId=None):
122 import teamcity.context_managers as cm
123 return cm.block(self, name=name, flowId=flowId)
125 def compilationStarted(self, compiler):
126 self.message('compilationStarted', compiler=compiler)
128 def compilationFinished(self, compiler):
129 self.message('compilationFinished', compiler=compiler)
131 def compilation(self, compiler):
132 import teamcity.context_managers as cm
133 return cm.compilation(self, compiler=compiler)
135 def testSuiteStarted(self, suiteName, flowId=None):
136 self.message('testSuiteStarted', name=suiteName, flowId=flowId)
138 def testSuiteFinished(self, suiteName, flowId=None):
139 self.message('testSuiteFinished', name=suiteName, flowId=flowId)
141 def testSuite(self, name):
142 import teamcity.context_managers as cm
143 return cm.testSuite(self, name=name)
145 def testStarted(self, testName, captureStandardOutput=None, flowId=None, metainfo=None):
146 """
148 :param metainfo: Used to pass any payload from test runner to Intellij. See IDEA-176950
149 """
150 self.message('testStarted', name=testName, captureStandardOutput=captureStandardOutput, flowId=flowId, metainfo=metainfo)
152 def testFinished(self, testName, testDuration=None, flowId=None):
153 if testDuration is not None:
154 duration_ms = testDuration.days * 86400000 + \
155 testDuration.seconds * 1000 + \
156 int(testDuration.microseconds / 1000)
157 self.message('testFinished', name=testName, duration=str(duration_ms), flowId=flowId)
158 else:
159 self.message('testFinished', name=testName, flowId=flowId)
161 def test(self, testName, captureStandardOutput=None, testDuration=None, flowId=None):
162 import teamcity.context_managers as cm
163 return cm.test(self, testName=testName, captureStandardOutput=captureStandardOutput, testDuration=testDuration, flowId=flowId)
165 # Unsupported in TeamCity, used in IntellIJ-based IDEs to predict number of tests to be run in the test session
166 def testCount(self, count, flowId=None):
167 self.message('testCount', count=str(count), flowId=flowId)
169 def testIgnored(self, testName, message='', flowId=None):
170 self.message('testIgnored', name=testName, message=message, flowId=flowId)
172 def testFailed(self, testName, message='', details='', flowId=None, comparison_failure=None):
173 if not comparison_failure:
174 self.message('testFailed', name=testName, message=message, details=details, flowId=flowId)
175 else:
176 diff_message = u"\n{0} != {1}\n".format(comparison_failure.actual, comparison_failure.expected)
177 self.message('testFailed',
178 name=testName,
179 message=text_type(message) + text_type(diff_message),
180 details=details,
181 flowId=flowId,
182 type="comparisonFailure",
183 actual=comparison_failure.actual,
184 expected=comparison_failure.expected)
186 def testStdOut(self, testName, out, flowId=None):
187 self.message('testStdOut', name=testName, out=out, flowId=flowId)
189 def testStdErr(self, testName, out, flowId=None):
190 self.message('testStdErr', name=testName, out=out, flowId=flowId)
192 def publishArtifacts(self, path, flowId=None):
193 self._single_value_message('publishArtifacts', path)
195 def progressMessage(self, message):
196 self._single_value_message('progressMessage', message)
198 def progressStart(self, message):
199 self._single_value_message('progressStart', message)
201 def progressFinish(self, message):
202 self._single_value_message('progressFinish', message)
204 def progress(self, message):
205 import teamcity.context_managers as cm
206 return cm.progress(self, message=message)
208 def buildProblem(self, description, identity):
209 self.message('buildProblem', description=description, identity=identity)
211 def buildStatus(self, status, text):
212 self.message('buildStatus', status=status, text=text)
214 def setParameter(self, name, value):
215 self.message('setParameter', name=name, value=value)
217 def buildStatisticLinesCovered(self, linesCovered):
218 self.message('buildStatisticValue', key='CodeCoverageAbsLCovered', value=str(linesCovered))
220 def buildStatisticTotalLines(self, totalLines):
221 self.message('buildStatisticValue', key='CodeCoverageAbsLTotal', value=str(totalLines))
223 def buildStatisticLinesUncovered(self, linesUncovered):
224 self.message('buildStatisticValue', key='CodeCoverageAbsLUncovered', value=str(linesUncovered))
226 def enableServiceMessages(self, flowId=None):
227 self.message('enableServiceMessages', flowId=flowId)
229 def disableServiceMessages(self, flowId=None):
230 self.message('disableServiceMessages', flowId=flowId)
232 def serviceMessagesDisabled(self, flowId=None):
233 import teamcity.context_managers as cm
234 return cm.serviceMessagesDisabled(self, flowId=flowId)
236 def serviceMessagesEnabled(self, flowId=None):
237 import teamcity.context_managers as cm
238 return cm.serviceMessagesEnabled(self, flowId=flowId)
240 def importData(self, typeID, pathToXMLFile):
241 self.message('importData', type=typeID, path=pathToXMLFile)
243 def customMessage(self, text, status, errorDetails='', flowId=None):
244 self.message('message', text=text, status=status, errorDetails=errorDetails, flowId=flowId)