1 package com
.intellij
.execution
.testframework
.sm
.runner
;
3 import com
.intellij
.execution
.process
.ProcessOutputTypes
;
4 import com
.intellij
.openapi
.diagnostic
.Logger
;
5 import com
.intellij
.openapi
.util
.Key
;
6 import com
.intellij
.openapi
.util
.text
.StringUtil
;
7 import jetbrains
.buildServer
.messages
.serviceMessages
.*;
8 import org
.jetbrains
.annotations
.NonNls
;
9 import org
.jetbrains
.annotations
.NotNull
;
10 import org
.jetbrains
.annotations
.Nullable
;
12 import java
.text
.ParseException
;
13 import java
.util
.ArrayList
;
14 import java
.util
.List
;
18 * @author Roman Chernyatchik
20 * This implementation also supports messages splitted in parts by early flush.
21 * Implementation assumes that buffer is being flushed on line end or by timer,
22 * i.e. incomming text contains no more than one line's end marker ('\r', '\n', or "\r\n")
23 * (e.g. process was run with IDEA program's runner)
25 public class OutputToGeneralTestEventsConverter
implements ProcessOutputConsumer
{
26 private static final Logger LOG
= Logger
.getInstance(OutputToGeneralTestEventsConverter
.class.getName());
28 private GeneralTestEventsProcessor myProcessor
;
29 private final MyServiceMessageVisitor myServiceMessageVisitor
;
31 private static class OutputChunk
{
33 private String myText
;
35 private OutputChunk(Key key
, String text
) {
44 public String
getText() {
48 public void append(String text
) {
53 private final List
<OutputChunk
> myOutputChunks
;
55 public OutputToGeneralTestEventsConverter() {
56 myServiceMessageVisitor
= new MyServiceMessageVisitor();
57 myOutputChunks
= new ArrayList
<OutputChunk
>();
60 public void setProcessor(final GeneralTestEventsProcessor processor
) {
61 myProcessor
= processor
;
64 public void process(final String text
, final Key outputType
) {
65 if (outputType
!= ProcessOutputTypes
.STDERR
&& outputType
!= ProcessOutputTypes
.SYSTEM
) {
66 // we check for consistensy only std output
67 // because all events must be send to stdout
68 processStdOutConsistently(text
, outputType
);
70 processConsistentText(text
, outputType
, false);
75 * Flashes the rest of stdout text buffer after output has been stopped
77 public void flushBufferBeforeTerminating() {
78 flushStdOutputBuffer();
81 public void dispose() {
85 private void flushStdOutputBuffer() {
86 // if osColoredProcessHandler was attached it can split string with several colors
87 // in several parts. Thus '\n' symbol may be send as one part with some color
88 // such situation should differ from single '\n' from process that is used by TC reporters
89 // to separate TC commands from other stuff + optimize flushing
90 // TODO: probably in IDEA mode such runners shouldn't add explicit \n because we can
91 // successfully process broken messages across several flushes
92 // size of parts may tell us either \n was single in original flushed data or it was
93 // separated by process handler
94 List
<OutputChunk
> chunks
= new ArrayList
<OutputChunk
>();
95 OutputChunk lastChunk
= null;
96 synchronized (myOutputChunks
) {
97 for (OutputChunk chunk
: myOutputChunks
) {
98 if (lastChunk
!= null && chunk
.getKey() == lastChunk
.getKey()) {
99 lastChunk
.append(chunk
.getText());
107 myOutputChunks
.clear();
109 final boolean isTCLikeFakeOutput
= chunks
.size() == 1;
110 for (OutputChunk chunk
: chunks
) {
111 processConsistentText(chunk
.getText(), chunk
.getKey(), isTCLikeFakeOutput
);
115 private void processStdOutConsistently(final String text
, final Key outputType
) {
116 final int textLength
= text
.length();
117 if (textLength
== 0) {
121 synchronized (myOutputChunks
) {
122 myOutputChunks
.add(new OutputChunk(outputType
, text
));
125 final char lastChar
= text
.charAt(textLength
- 1);
126 if (lastChar
== '\n' || lastChar
== '\r') {
127 // buffer contains consistent string
128 flushStdOutputBuffer();
132 private void processConsistentText(final String text
, final Key outputType
, boolean tcLikeFakeOutput
) {
134 final ServiceMessage serviceMessage
= ServiceMessage
.parse(text
);
135 if (serviceMessage
!= null) {
136 serviceMessage
.visit(myServiceMessageVisitor
);
139 if (text
.equals("\n") && tcLikeFakeOutput
) {
140 // ServiceMessages protocol requires that every message
141 // should start with new line, so such behaviour may led to generating
142 // some number of useless \n.
144 // This will not affect tests output because all
145 // output will be in TestOutput message
148 //fire current output
149 fireOnUncapturedOutput(text
, outputType
);
152 catch (ParseException e
) {
157 private void fireOnTestStarted(final String testName
, @Nullable final String locationUrl
) {
158 // local variable is used to prevent concurrent modification
159 final GeneralTestEventsProcessor processor
= myProcessor
;
160 if (processor
!= null) {
161 processor
.onTestStarted(testName
, locationUrl
);
165 private void fireOnTestFailure(final String testName
, final String localizedMessage
, final String stackTrace
,
166 final boolean isTestError
) {
168 // local variable is used to prevent concurrent modification
169 final GeneralTestEventsProcessor processor
= myProcessor
;
170 if (processor
!= null) {
171 processor
.onTestFailure(testName
, localizedMessage
, stackTrace
, isTestError
);
175 private void fireOnTestIgnored(final String testName
, final String ignoreComment
,
176 @Nullable final String details
) {
178 // local variable is used to prevent concurrent modification
179 final GeneralTestEventsProcessor processor
= myProcessor
;
180 if (processor
!= null) {
181 processor
.onTestIgnored(testName
, ignoreComment
, details
);
185 private void fireOnTestFinished(final String testName
, final int duration
) {
186 // local variable is used to prevent concurrent modification
187 final GeneralTestEventsProcessor processor
= myProcessor
;
188 if (processor
!= null) {
189 processor
.onTestFinished(testName
, duration
);
193 private void fireOnCustomProgressTestsCategory(@NotNull final String categoryName
, int testsCount
) {
194 final GeneralTestEventsProcessor processor
= myProcessor
;
195 if (processor
!= null) {
196 final boolean disableCustomMode
= StringUtil
.isEmpty(categoryName
);
197 processor
.onCustomProgressTestsCategory(disableCustomMode ?
null : categoryName
,
198 disableCustomMode ?
0 : testsCount
);
202 private void fireOnCustomProgressTestStarted() {
203 final GeneralTestEventsProcessor processor
= myProcessor
;
204 if (processor
!= null) {
205 processor
.onCustomProgressTestStarted();
209 private void fireOnCustomProgressTestFailed() {
210 final GeneralTestEventsProcessor processor
= myProcessor
;
211 if (processor
!= null) {
212 processor
.onCustomProgressTestFailed();
216 private void fireOnTestOutput(final String testName
, final String text
, final boolean stdOut
) {
217 // local variable is used to prevent concurrent modification
218 final GeneralTestEventsProcessor processor
= myProcessor
;
219 if (processor
!= null) {
220 processor
.onTestOutput(testName
, text
, stdOut
);
224 private void fireOnUncapturedOutput(final String text
, final Key outputType
) {
225 // local variable is used to prevent concurrent modification
226 final GeneralTestEventsProcessor processor
= myProcessor
;
227 if (processor
!= null) {
228 processor
.onUncapturedOutput(text
, outputType
);
232 private void fireOnTestsCountInSuite(final int count
) {
233 // local variable is used to prevent concurrent modification
234 final GeneralTestEventsProcessor processor
= myProcessor
;
235 if (processor
!= null) {
236 processor
.onTestsCountInSuite(count
);
240 private void fireOnSuiteStarted(final String suiteName
, @Nullable final String locationUrl
) {
241 // local variable is used to prevent concurrent modification
242 final GeneralTestEventsProcessor processor
= myProcessor
;
243 if (processor
!= null) {
244 processor
.onSuiteStarted(suiteName
, locationUrl
);
248 private void fireOnSuiteFinished(final String suiteName
) {
249 // local variable is used to prevent concurrent modification
250 final GeneralTestEventsProcessor processor
= myProcessor
;
251 if (processor
!= null) {
252 processor
.onSuiteFinished(suiteName
);
256 private class MyServiceMessageVisitor
extends DefaultServiceMessageVisitor
{
257 @NonNls public static final String KEY_TESTS_COUNT
= "testCount";
258 @NonNls private static final String ATTR_KEY_TEST_ERROR
= "error";
259 @NonNls private static final String ATTR_KEY_TEST_COUNT
= "count";
260 @NonNls private static final String ATTR_KEY_TEST_DURATION
= "duration";
261 @NonNls private static final String ATTR_KEY_LOCATION_URL
= "locationHint";
262 @NonNls private static final String ATTR_KEY_LOCATION_URL_OLD
= "location";
263 @NonNls private static final String ATTR_KEY_STACKTRACE_DETAILS
= "details";
265 @NonNls public static final String CUSTOM_STATUS
= "customProgressStatus";
266 @NonNls private static final String ATTR_KEY_TEST_TYPE
= "type";
267 @NonNls private static final String ATTR_KEY_TESTS_CATEGORY
= "testsCategory";
268 @NonNls private static final String ATTR_VAL_TEST_STARTED
= "testStarted";
269 @NonNls private static final String ATTR_VAL_TEST_FAILED
= "testFailed";
271 public void visitTestSuiteStarted(@NotNull final TestSuiteStarted suiteStarted
) {
272 final String locationUrl
= fetchTestLocation(suiteStarted
);
273 fireOnSuiteStarted(suiteStarted
.getSuiteName(), locationUrl
);
277 private String
fetchTestLocation(TestSuiteStarted suiteStarted
) {
278 final Map
<String
, String
> attrs
= suiteStarted
.getAttributes();
279 final String location
= attrs
.get(ATTR_KEY_LOCATION_URL
);
280 if (location
== null) {
282 final String oldLocation
= attrs
.get(ATTR_KEY_LOCATION_URL_OLD
);
283 if (oldLocation
!= null) {
284 LOG
.error("Test Runner API was changed for TeamCity 5.0 compatibility. Please use 'locationHint' attribute instead of 'location'.");
292 public void visitTestSuiteFinished(@NotNull final TestSuiteFinished suiteFinished
) {
293 fireOnSuiteFinished(suiteFinished
.getSuiteName());
296 public void visitTestStarted(@NotNull final TestStarted testStarted
) {
297 final String locationUrl
= testStarted
.getAttributes().get(ATTR_KEY_LOCATION_URL
);
298 fireOnTestStarted(testStarted
.getTestName(), locationUrl
);
301 public void visitTestFinished(@NotNull final TestFinished testFinished
) {
303 //final Integer duration = testFinished.getTestDuration();
304 //fireOnTestFinished(testFinished.getTestName(), duration != null ? duration.intValue() : 0);
306 final String durationStr
= testFinished
.getAttributes().get(ATTR_KEY_TEST_DURATION
);
308 // Test's duration in milliseconds
311 if (!StringUtil
.isEmptyOrSpaces(durationStr
)) {
313 duration
= Integer
.parseInt(durationStr
);
314 } catch (NumberFormatException ex
) {
319 fireOnTestFinished(testFinished
.getTestName(), duration
);
322 public void visitTestIgnored(@NotNull final TestIgnored testIgnored
) {
323 final String details
= testIgnored
.getAttributes().get(ATTR_KEY_STACKTRACE_DETAILS
);
324 fireOnTestIgnored(testIgnored
.getTestName(), testIgnored
.getIgnoreComment(), details
);
327 public void visitTestStdOut(@NotNull final TestStdOut testStdOut
) {
328 fireOnTestOutput(testStdOut
.getTestName(),testStdOut
.getStdOut(), true);
331 public void visitTestStdErr(@NotNull final TestStdErr testStdErr
) {
332 fireOnTestOutput(testStdErr
.getTestName(),testStdErr
.getStdErr(), false);
335 public void visitTestFailed(@NotNull final TestFailed testFailed
) {
336 final boolean isTestError
= testFailed
.getAttributes().get(ATTR_KEY_TEST_ERROR
) != null;
338 fireOnTestFailure(testFailed
.getTestName(), testFailed
.getFailureMessage(), testFailed
.getStacktrace(), isTestError
);
341 public void visitPublishArtifacts(@NotNull final PublishArtifacts publishArtifacts
) {
345 public void visitProgressMessage(@NotNull final ProgressMessage progressMessage
) {
349 public void visitProgressStart(@NotNull final ProgressStart progressStart
) {
353 public void visitProgressFinish(@NotNull final ProgressFinish progressFinish
) {
357 public void visitBuildStatus(@NotNull final BuildStatus buildStatus
) {
361 public void visitBuildNumber(@NotNull final BuildNumber buildNumber
) {
365 public void visitBuildStatisticValue(@NotNull final BuildStatisticValue buildStatsValue
) {
369 public void visitServiceMessage(@NotNull final ServiceMessage msg
) {
370 final String name
= msg
.getMessageName();
372 if (KEY_TESTS_COUNT
.equals(name
)) {
373 processTestCountInSuite(msg
);
374 } else if (CUSTOM_STATUS
.equals(name
)) {
375 processCustomStatus(msg
);
381 private void processTestCountInSuite(final ServiceMessage msg
) {
382 final String countStr
= msg
.getAttributes().get(ATTR_KEY_TEST_COUNT
);
383 fireOnTestsCountInSuite(convertToInt(countStr
));
386 private int convertToInt(String countStr
) {
389 count
= Integer
.parseInt(countStr
);
390 } catch (NumberFormatException ex
) {
396 private void processCustomStatus(final ServiceMessage msg
) {
397 final Map
<String
,String
> attrs
= msg
.getAttributes();
398 final String msgType
= attrs
.get(ATTR_KEY_TEST_TYPE
);
399 if (msgType
!= null) {
400 if (msgType
.equals(ATTR_VAL_TEST_STARTED
)) {
401 fireOnCustomProgressTestStarted();
402 } else if (msgType
.equals(ATTR_VAL_TEST_FAILED
)) {
403 fireOnCustomProgressTestFailed();
407 final String testsCategory
= attrs
.get(ATTR_KEY_TESTS_CATEGORY
);
408 if (testsCategory
!= null) {
409 final String countStr
= msg
.getAttributes().get(ATTR_KEY_TEST_COUNT
);
410 fireOnCustomProgressTestsCategory(testsCategory
, convertToInt(countStr
));
412 //noinspection UnnecessaryReturnStatement