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
;
17 * @author Roman Chernyatchik
19 * This implementation also supports messages splitted in parts by early flush.
20 * Implementation assumes that buffer is being flushed on line end or by timer,
21 * i.e. incomming text contains no more than one line's end marker ('\r', '\n', or "\r\n")
22 * (e.g. process was run with IDEA program's runner)
24 public class OutputToGeneralTestEventsConverter
implements ProcessOutputConsumer
{
25 private static final Logger LOG
= Logger
.getInstance(OutputToGeneralTestEventsConverter
.class.getName());
27 private GeneralTestEventsProcessor myProcessor
;
28 private final MyServiceMessageVisitor myServiceMessageVisitor
;
30 private static class OutputChunk
{
32 private String myText
;
34 private OutputChunk(Key key
, String text
) {
43 public String
getText() {
47 public void append(String text
) {
52 private final List
<OutputChunk
> myOutputChunks
;
54 public OutputToGeneralTestEventsConverter() {
55 myServiceMessageVisitor
= new MyServiceMessageVisitor();
56 myOutputChunks
= new ArrayList
<OutputChunk
>();
59 public void setProcessor(final GeneralTestEventsProcessor processor
) {
60 myProcessor
= processor
;
63 public void process(final String text
, final Key outputType
) {
64 if (outputType
!= ProcessOutputTypes
.STDERR
&& outputType
!= ProcessOutputTypes
.SYSTEM
) {
65 // we check for consistensy only std output
66 // because all events must be send to stdout
67 processStdOutConsistently(text
, outputType
);
69 processConsistentText(text
, outputType
, false);
74 * Flashes the rest of stdout text buffer after output has been stopped
76 public void flushBufferBeforeTerminating() {
77 flushStdOutputBuffer();
80 public void dispose() {
84 private void flushStdOutputBuffer() {
85 // if osColoredProcessHandler was attached it can split string with several colors
86 // in several parts. Thus '\n' symbol may be send as one part with some color
87 // such situation should differ from single '\n' from process that is used by TC reporters
88 // to separate TC commands from other stuff + optimize flushing
89 // TODO: probably in IDEA mode such runners shouldn't add explicit \n because we can
90 // successfully process broken messages across several flushes
91 // size of parts may tell us either \n was single in original flushed data or it was
92 // separated by process handler
93 List
<OutputChunk
> chunks
= new ArrayList
<OutputChunk
>();
94 OutputChunk lastChunk
= null;
95 synchronized (myOutputChunks
) {
96 for (OutputChunk chunk
: myOutputChunks
) {
97 if (lastChunk
!= null && chunk
.getKey() == lastChunk
.getKey()) {
98 lastChunk
.append(chunk
.getText());
106 myOutputChunks
.clear();
108 final boolean isTCLikeFakeOutput
= chunks
.size() == 1;
109 for (OutputChunk chunk
: chunks
) {
110 processConsistentText(chunk
.getText(), chunk
.getKey(), isTCLikeFakeOutput
);
114 private void processStdOutConsistently(final String text
, final Key outputType
) {
115 final int textLength
= text
.length();
116 if (textLength
== 0) {
120 synchronized (myOutputChunks
) {
121 myOutputChunks
.add(new OutputChunk(outputType
, text
));
124 final char lastChar
= text
.charAt(textLength
- 1);
125 if (lastChar
== '\n' || lastChar
== '\r') {
126 // buffer contains consistent string
127 flushStdOutputBuffer();
131 private void processConsistentText(final String text
, final Key outputType
, boolean tcLikeFakeOutput
) {
133 final ServiceMessage serviceMessage
= ServiceMessage
.parse(text
);
134 if (serviceMessage
!= null) {
135 serviceMessage
.visit(myServiceMessageVisitor
);
138 if (text
.equals("\n") && tcLikeFakeOutput
) {
139 // ServiceMessages protocol requires that every message
140 // should start with new line, so such behaviour may led to generating
141 // some number of useless \n.
143 // This will not affect tests output because all
144 // output will be in TestOutput message
147 //fire current output
148 fireOnUncapturedOutput(text
, outputType
);
151 catch (ParseException e
) {
156 private void fireOnTestStarted(final String testName
, @Nullable final String locationUrl
) {
157 // local variable is used to prevent concurrent modification
158 final GeneralTestEventsProcessor processor
= myProcessor
;
159 if (processor
!= null) {
160 processor
.onTestStarted(testName
, locationUrl
);
164 private void fireOnTestFailure(final String testName
, final String localizedMessage
, final String stackTrace
,
165 final boolean isTestError
) {
167 // local variable is used to prevent concurrent modification
168 final GeneralTestEventsProcessor processor
= myProcessor
;
169 if (processor
!= null) {
170 processor
.onTestFailure(testName
, localizedMessage
, stackTrace
, isTestError
);
174 private void fireOnTestIgnored(final String testName
, final String ignoreComment
,
175 @Nullable final String details
) {
177 // local variable is used to prevent concurrent modification
178 final GeneralTestEventsProcessor processor
= myProcessor
;
179 if (processor
!= null) {
180 processor
.onTestIgnored(testName
, ignoreComment
, details
);
184 private void fireOnTestOutput(final String testName
, final String text
, final boolean stdOut
) {
185 // local variable is used to prevent concurrent modification
186 final GeneralTestEventsProcessor processor
= myProcessor
;
187 if (processor
!= null) {
188 processor
.onTestOutput(testName
, text
, stdOut
);
192 private void fireOnUncapturedOutput(final String text
, final Key outputType
) {
193 // local variable is used to prevent concurrent modification
194 final GeneralTestEventsProcessor processor
= myProcessor
;
195 if (processor
!= null) {
196 processor
.onUncapturedOutput(text
, outputType
);
200 private void fireOnTestsCountInSuite(final int count
) {
201 // local variable is used to prevent concurrent modification
202 final GeneralTestEventsProcessor processor
= myProcessor
;
203 if (processor
!= null) {
204 processor
.onTestsCountInSuite(count
);
208 private void fireOnTestFinished(final String testName
, final int duration
) {
209 // local variable is used to prevent concurrent modification
210 final GeneralTestEventsProcessor processor
= myProcessor
;
211 if (processor
!= null) {
212 processor
.onTestFinished(testName
, duration
);
216 private void fireOnSuiteStarted(final String suiteName
, @Nullable final String locationUrl
) {
217 // local variable is used to prevent concurrent modification
218 final GeneralTestEventsProcessor processor
= myProcessor
;
219 if (processor
!= null) {
220 processor
.onSuiteStarted(suiteName
, locationUrl
);
224 private void fireOnSuiteFinished(final String suiteName
) {
225 // local variable is used to prevent concurrent modification
226 final GeneralTestEventsProcessor processor
= myProcessor
;
227 if (processor
!= null) {
228 processor
.onSuiteFinished(suiteName
);
232 private class MyServiceMessageVisitor
extends DefaultServiceMessageVisitor
{
233 @NonNls public static final String KEY_TESTS_COUNT
= "testCount";
234 @NonNls private static final String ATTR_KEY_TEST_ERROR
= "error";
235 @NonNls private static final String ATTR_KEY_TEST_COUNT
= "count";
236 @NonNls private static final String ATTR_KEY_TEST_DURATION
= "duration";
237 @NonNls private static final String ATTR_KEY_LOCATION_URL
= "location";
238 @NonNls private static final String ATTR_KEY_STACKTRACE_DETAILS
= "details";
240 public void visitTestSuiteStarted(@NotNull final TestSuiteStarted suiteStarted
) {
241 final String locationUrl
= suiteStarted
.getAttributes().get(ATTR_KEY_LOCATION_URL
);
242 fireOnSuiteStarted(suiteStarted
.getSuiteName(), locationUrl
);
245 public void visitTestSuiteFinished(@NotNull final TestSuiteFinished suiteFinished
) {
246 fireOnSuiteFinished(suiteFinished
.getSuiteName());
249 public void visitTestStarted(@NotNull final TestStarted testStarted
) {
250 final String locationUrl
= testStarted
.getAttributes().get(ATTR_KEY_LOCATION_URL
);
251 fireOnTestStarted(testStarted
.getTestName(), locationUrl
);
254 public void visitTestFinished(@NotNull final TestFinished testFinished
) {
256 //final Integer duration = testFinished.getTestDuration();
257 //fireOnTestFinished(testFinished.getTestName(), duration != null ? duration.intValue() : 0);
259 final String durationStr
= testFinished
.getAttributes().get(ATTR_KEY_TEST_DURATION
);
261 // Test's duration in milliseconds
264 if (!StringUtil
.isEmptyOrSpaces(durationStr
)) {
266 duration
= Integer
.parseInt(durationStr
);
267 } catch (NumberFormatException ex
) {
272 fireOnTestFinished(testFinished
.getTestName(), duration
);
275 public void visitTestIgnored(@NotNull final TestIgnored testIgnored
) {
276 final String details
= testIgnored
.getAttributes().get(ATTR_KEY_STACKTRACE_DETAILS
);
277 fireOnTestIgnored(testIgnored
.getTestName(), testIgnored
.getIgnoreComment(), details
);
280 public void visitTestStdOut(@NotNull final TestStdOut testStdOut
) {
281 fireOnTestOutput(testStdOut
.getTestName(),testStdOut
.getStdOut(), true);
284 public void visitTestStdErr(@NotNull final TestStdErr testStdErr
) {
285 fireOnTestOutput(testStdErr
.getTestName(),testStdErr
.getStdErr(), false);
288 public void visitTestFailed(@NotNull final TestFailed testFailed
) {
289 final boolean isTestError
= testFailed
.getAttributes().get(ATTR_KEY_TEST_ERROR
) != null;
291 fireOnTestFailure(testFailed
.getTestName(), testFailed
.getFailureMessage(), testFailed
.getStacktrace(), isTestError
);
294 public void visitPublishArtifacts(@NotNull final PublishArtifacts publishArtifacts
) {
298 public void visitProgressMessage(@NotNull final ProgressMessage progressMessage
) {
302 public void visitProgressStart(@NotNull final ProgressStart progressStart
) {
306 public void visitProgressFinish(@NotNull final ProgressFinish progressFinish
) {
310 public void visitBuildStatus(@NotNull final BuildStatus buildStatus
) {
314 public void visitBuildNumber(@NotNull final BuildNumber buildNumber
) {
318 public void visitBuildStatisticValue(@NotNull final BuildStatisticValue buildStatsValue
) {
322 public void visitServiceMessage(@NotNull final ServiceMessage msg
) {
323 final String name
= msg
.getMessageName();
325 if (KEY_TESTS_COUNT
.equals(name
)) {
326 processTestCountInSuite(msg
);
332 private void processTestCountInSuite(final ServiceMessage msg
) {
333 final String countStr
= msg
.getAttributes().get(ATTR_KEY_TEST_COUNT
);
336 count
= Integer
.parseInt(countStr
);
337 } catch (NumberFormatException ex
) {
340 fireOnTestsCountInSuite(count
);