ConcurrentModificationException in SM Runner fixed
[fedora-idea.git] / smRunner / src / com / intellij / execution / testframework / sm / runner / OutputToGeneralTestEventsConverter.java
blob6963509ef41dd81e178a9d0e1c03ecbd7ff2d07c
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;
16 /**
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 {
31 private Key myKey;
32 private String myText;
34 private OutputChunk(Key key, String text) {
35 myKey = key;
36 myText = text;
39 public Key getKey() {
40 return myKey;
43 public String getText() {
44 return myText;
47 public void append(String text) {
48 myText += 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);
68 } else {
69 processConsistentText(text, outputType, false);
73 /**
74 * Flashes the rest of stdout text buffer after output has been stopped
76 public void flushBufferBeforeTerminating() {
77 flushStdOutputBuffer();
80 public void dispose() {
81 setProcessor(null);
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());
100 else {
101 lastChunk = chunk;
102 chunks.add(chunk);
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) {
117 return;
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) {
132 try {
133 final ServiceMessage serviceMessage = ServiceMessage.parse(text);
134 if (serviceMessage != null) {
135 serviceMessage.visit(myServiceMessageVisitor);
136 } else {
137 // Filters \n
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
145 return;
147 //fire current output
148 fireOnUncapturedOutput(text, outputType);
151 catch (ParseException e) {
152 LOG.error(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) {
255 //TODO
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
262 int duration = 0;
264 if (!StringUtil.isEmptyOrSpaces(durationStr)) {
265 try {
266 duration = Integer.parseInt(durationStr);
267 } catch (NumberFormatException ex) {
268 LOG.error(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) {
295 //Do nothing
298 public void visitProgressMessage(@NotNull final ProgressMessage progressMessage) {
299 //Do nothing
302 public void visitProgressStart(@NotNull final ProgressStart progressStart) {
303 //Do nothing
306 public void visitProgressFinish(@NotNull final ProgressFinish progressFinish) {
307 //Do nothing
310 public void visitBuildStatus(@NotNull final BuildStatus buildStatus) {
311 //Do nothing
314 public void visitBuildNumber(@NotNull final BuildNumber buildNumber) {
315 //Do nothing
318 public void visitBuildStatisticValue(@NotNull final BuildStatisticValue buildStatsValue) {
319 //Do nothing
322 public void visitServiceMessage(@NotNull final ServiceMessage msg) {
323 final String name = msg.getMessageName();
325 if (KEY_TESTS_COUNT.equals(name)) {
326 processTestCountInSuite(msg);
327 } else {
328 //Do nothing
332 private void processTestCountInSuite(final ServiceMessage msg) {
333 final String countStr = msg.getAttributes().get(ATTR_KEY_TEST_COUNT);
334 int count = 0;
335 try {
336 count = Integer.parseInt(countStr);
337 } catch (NumberFormatException ex) {
338 LOG.error(ex);
340 fireOnTestsCountInSuite(count);