ComponentWithBrowseButton - optional remove listener on hide
[fedora-idea.git] / platform / smRunner / src / com / intellij / execution / testframework / sm / runner / OutputToGeneralTestEventsConverter.java
blobb08db008f2cea0d9bc43a15b9e784f58b0e10704
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.execution.testframework.sm.runner;
18 import com.intellij.execution.process.ProcessOutputTypes;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.Key;
21 import com.intellij.openapi.util.text.StringUtil;
22 import jetbrains.buildServer.messages.serviceMessages.*;
23 import org.jetbrains.annotations.NonNls;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
27 import java.text.ParseException;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.Map;
32 /**
33 * @author Roman Chernyatchik
35 * This implementation also supports messages splitted in parts by early flush.
36 * Implementation assumes that buffer is being flushed on line end or by timer,
37 * i.e. incomming text contains no more than one line's end marker ('\r', '\n', or "\r\n")
38 * (e.g. process was run with IDEA program's runner)
40 public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer {
41 private static final Logger LOG = Logger.getInstance(OutputToGeneralTestEventsConverter.class.getName());
43 private GeneralTestEventsProcessor myProcessor;
44 private final MyServiceMessageVisitor myServiceMessageVisitor;
46 private static class OutputChunk {
47 private Key myKey;
48 private String myText;
50 private OutputChunk(Key key, String text) {
51 myKey = key;
52 myText = text;
55 public Key getKey() {
56 return myKey;
59 public String getText() {
60 return myText;
63 public void append(String text) {
64 myText += text;
68 private final List<OutputChunk> myOutputChunks;
70 public OutputToGeneralTestEventsConverter() {
71 myServiceMessageVisitor = new MyServiceMessageVisitor();
72 myOutputChunks = new ArrayList<OutputChunk>();
75 public void setProcessor(final GeneralTestEventsProcessor processor) {
76 myProcessor = processor;
79 public void process(final String text, final Key outputType) {
80 if (outputType != ProcessOutputTypes.STDERR && outputType != ProcessOutputTypes.SYSTEM) {
81 // we check for consistensy only std output
82 // because all events must be send to stdout
83 processStdOutConsistently(text, outputType);
84 } else {
85 processConsistentText(text, outputType, false);
89 /**
90 * Flashes the rest of stdout text buffer after output has been stopped
92 public void flushBufferBeforeTerminating() {
93 flushStdOutputBuffer();
96 public void dispose() {
97 setProcessor(null);
100 private void flushStdOutputBuffer() {
101 // if osColoredProcessHandler was attached it can split string with several colors
102 // in several parts. Thus '\n' symbol may be send as one part with some color
103 // such situation should differ from single '\n' from process that is used by TC reporters
104 // to separate TC commands from other stuff + optimize flushing
105 // TODO: probably in IDEA mode such runners shouldn't add explicit \n because we can
106 // successfully process broken messages across several flushes
107 // size of parts may tell us either \n was single in original flushed data or it was
108 // separated by process handler
109 List<OutputChunk> chunks = new ArrayList<OutputChunk>();
110 OutputChunk lastChunk = null;
111 synchronized (myOutputChunks) {
112 for (OutputChunk chunk : myOutputChunks) {
113 if (lastChunk != null && chunk.getKey() == lastChunk.getKey()) {
114 lastChunk.append(chunk.getText());
116 else {
117 lastChunk = chunk;
118 chunks.add(chunk);
122 myOutputChunks.clear();
124 final boolean isTCLikeFakeOutput = chunks.size() == 1;
125 for (OutputChunk chunk : chunks) {
126 processConsistentText(chunk.getText(), chunk.getKey(), isTCLikeFakeOutput);
130 private void processStdOutConsistently(final String text, final Key outputType) {
131 final int textLength = text.length();
132 if (textLength == 0) {
133 return;
136 synchronized (myOutputChunks) {
137 myOutputChunks.add(new OutputChunk(outputType, text));
140 final char lastChar = text.charAt(textLength - 1);
141 if (lastChar == '\n' || lastChar == '\r') {
142 // buffer contains consistent string
143 flushStdOutputBuffer();
147 private void processConsistentText(final String text, final Key outputType, boolean tcLikeFakeOutput) {
148 try {
149 final ServiceMessage serviceMessage = ServiceMessage.parse(text);
150 if (serviceMessage != null) {
151 serviceMessage.visit(myServiceMessageVisitor);
152 } else {
153 // Filters \n
154 if (text.equals("\n") && tcLikeFakeOutput) {
155 // ServiceMessages protocol requires that every message
156 // should start with new line, so such behaviour may led to generating
157 // some number of useless \n.
159 // This will not affect tests output because all
160 // output will be in TestOutput message
161 return;
163 //fire current output
164 fireOnUncapturedOutput(text, outputType);
167 catch (ParseException e) {
168 LOG.error(e);
172 private void fireOnTestStarted(final String testName, @Nullable final String locationUrl) {
173 // local variable is used to prevent concurrent modification
174 final GeneralTestEventsProcessor processor = myProcessor;
175 if (processor != null) {
176 processor.onTestStarted(testName, locationUrl);
180 private void fireOnTestFailure(final String testName, final String localizedMessage, final String stackTrace,
181 final boolean isTestError) {
183 // local variable is used to prevent concurrent modification
184 final GeneralTestEventsProcessor processor = myProcessor;
185 if (processor != null) {
186 processor.onTestFailure(testName, localizedMessage, stackTrace, isTestError);
190 private void fireOnTestIgnored(final String testName, final String ignoreComment,
191 @Nullable final String details) {
193 // local variable is used to prevent concurrent modification
194 final GeneralTestEventsProcessor processor = myProcessor;
195 if (processor != null) {
196 processor.onTestIgnored(testName, ignoreComment, details);
200 private void fireOnTestFinished(final String testName, final int duration) {
201 // local variable is used to prevent concurrent modification
202 final GeneralTestEventsProcessor processor = myProcessor;
203 if (processor != null) {
204 processor.onTestFinished(testName, duration);
208 private void fireOnCustomProgressTestsCategory(@NotNull final String categoryName, int testsCount) {
209 final GeneralTestEventsProcessor processor = myProcessor;
210 if (processor != null) {
211 final boolean disableCustomMode = StringUtil.isEmpty(categoryName);
212 processor.onCustomProgressTestsCategory(disableCustomMode ? null : categoryName,
213 disableCustomMode ? 0 : testsCount);
217 private void fireOnCustomProgressTestStarted() {
218 final GeneralTestEventsProcessor processor = myProcessor;
219 if (processor != null) {
220 processor.onCustomProgressTestStarted();
224 private void fireOnCustomProgressTestFailed() {
225 final GeneralTestEventsProcessor processor = myProcessor;
226 if (processor != null) {
227 processor.onCustomProgressTestFailed();
231 private void fireOnTestOutput(final String testName, final String text, final boolean stdOut) {
232 // local variable is used to prevent concurrent modification
233 final GeneralTestEventsProcessor processor = myProcessor;
234 if (processor != null) {
235 processor.onTestOutput(testName, text, stdOut);
239 private void fireOnUncapturedOutput(final String text, final Key outputType) {
240 // local variable is used to prevent concurrent modification
241 final GeneralTestEventsProcessor processor = myProcessor;
242 if (processor != null) {
243 processor.onUncapturedOutput(text, outputType);
247 private void fireOnTestsCountInSuite(final int count) {
248 // local variable is used to prevent concurrent modification
249 final GeneralTestEventsProcessor processor = myProcessor;
250 if (processor != null) {
251 processor.onTestsCountInSuite(count);
255 private void fireOnSuiteStarted(final String suiteName, @Nullable final String locationUrl) {
256 // local variable is used to prevent concurrent modification
257 final GeneralTestEventsProcessor processor = myProcessor;
258 if (processor != null) {
259 processor.onSuiteStarted(suiteName, locationUrl);
263 private void fireOnSuiteFinished(final String suiteName) {
264 // local variable is used to prevent concurrent modification
265 final GeneralTestEventsProcessor processor = myProcessor;
266 if (processor != null) {
267 processor.onSuiteFinished(suiteName);
271 private class MyServiceMessageVisitor extends DefaultServiceMessageVisitor {
272 @NonNls public static final String KEY_TESTS_COUNT = "testCount";
273 @NonNls private static final String ATTR_KEY_TEST_ERROR = "error";
274 @NonNls private static final String ATTR_KEY_TEST_COUNT = "count";
275 @NonNls private static final String ATTR_KEY_TEST_DURATION = "duration";
276 @NonNls private static final String ATTR_KEY_LOCATION_URL = "locationHint";
277 @NonNls private static final String ATTR_KEY_LOCATION_URL_OLD = "location";
278 @NonNls private static final String ATTR_KEY_STACKTRACE_DETAILS = "details";
280 @NonNls public static final String CUSTOM_STATUS = "customProgressStatus";
281 @NonNls private static final String ATTR_KEY_TEST_TYPE = "type";
282 @NonNls private static final String ATTR_KEY_TESTS_CATEGORY = "testsCategory";
283 @NonNls private static final String ATTR_VAL_TEST_STARTED = "testStarted";
284 @NonNls private static final String ATTR_VAL_TEST_FAILED = "testFailed";
286 public void visitTestSuiteStarted(@NotNull final TestSuiteStarted suiteStarted) {
287 final String locationUrl = fetchTestLocation(suiteStarted);
288 fireOnSuiteStarted(suiteStarted.getSuiteName(), locationUrl);
291 @Nullable
292 private String fetchTestLocation(TestSuiteStarted suiteStarted) {
293 final Map<String, String> attrs = suiteStarted.getAttributes();
294 final String location = attrs.get(ATTR_KEY_LOCATION_URL);
295 if (location == null) {
296 // try old API
297 final String oldLocation = attrs.get(ATTR_KEY_LOCATION_URL_OLD);
298 if (oldLocation != null) {
299 LOG.error("Test Runner API was changed for TeamCity 5.0 compatibility. Please use 'locationHint' attribute instead of 'location'.");
300 return oldLocation;
302 return null;
304 return location;
307 public void visitTestSuiteFinished(@NotNull final TestSuiteFinished suiteFinished) {
308 fireOnSuiteFinished(suiteFinished.getSuiteName());
311 public void visitTestStarted(@NotNull final TestStarted testStarted) {
312 final String locationUrl = testStarted.getAttributes().get(ATTR_KEY_LOCATION_URL);
313 fireOnTestStarted(testStarted.getTestName(), locationUrl);
316 public void visitTestFinished(@NotNull final TestFinished testFinished) {
317 //TODO
318 //final Integer duration = testFinished.getTestDuration();
319 //fireOnTestFinished(testFinished.getTestName(), duration != null ? duration.intValue() : 0);
321 final String durationStr = testFinished.getAttributes().get(ATTR_KEY_TEST_DURATION);
323 // Test's duration in milliseconds
324 int duration = 0;
326 if (!StringUtil.isEmptyOrSpaces(durationStr)) {
327 try {
328 duration = Integer.parseInt(durationStr);
329 } catch (NumberFormatException ex) {
330 LOG.error(ex);
334 fireOnTestFinished(testFinished.getTestName(), duration);
337 public void visitTestIgnored(@NotNull final TestIgnored testIgnored) {
338 final String details = testIgnored.getAttributes().get(ATTR_KEY_STACKTRACE_DETAILS);
339 fireOnTestIgnored(testIgnored.getTestName(), testIgnored.getIgnoreComment(), details);
342 public void visitTestStdOut(@NotNull final TestStdOut testStdOut) {
343 fireOnTestOutput(testStdOut.getTestName(),testStdOut.getStdOut(), true);
346 public void visitTestStdErr(@NotNull final TestStdErr testStdErr) {
347 fireOnTestOutput(testStdErr.getTestName(),testStdErr.getStdErr(), false);
350 public void visitTestFailed(@NotNull final TestFailed testFailed) {
351 final boolean isTestError = testFailed.getAttributes().get(ATTR_KEY_TEST_ERROR) != null;
353 fireOnTestFailure(testFailed.getTestName(), testFailed.getFailureMessage(), testFailed.getStacktrace(), isTestError);
356 public void visitPublishArtifacts(@NotNull final PublishArtifacts publishArtifacts) {
357 //Do nothing
360 public void visitProgressMessage(@NotNull final ProgressMessage progressMessage) {
361 //Do nothing
364 public void visitProgressStart(@NotNull final ProgressStart progressStart) {
365 //Do nothing
368 public void visitProgressFinish(@NotNull final ProgressFinish progressFinish) {
369 //Do nothing
372 public void visitBuildStatus(@NotNull final BuildStatus buildStatus) {
373 //Do nothing
376 public void visitBuildNumber(@NotNull final BuildNumber buildNumber) {
377 //Do nothing
380 public void visitBuildStatisticValue(@NotNull final BuildStatisticValue buildStatsValue) {
381 //Do nothing
384 public void visitServiceMessage(@NotNull final ServiceMessage msg) {
385 final String name = msg.getMessageName();
387 if (KEY_TESTS_COUNT.equals(name)) {
388 processTestCountInSuite(msg);
389 } else if (CUSTOM_STATUS.equals(name)) {
390 processCustomStatus(msg);
391 } else {
392 //Do nothing
396 private void processTestCountInSuite(final ServiceMessage msg) {
397 final String countStr = msg.getAttributes().get(ATTR_KEY_TEST_COUNT);
398 fireOnTestsCountInSuite(convertToInt(countStr));
401 private int convertToInt(String countStr) {
402 int count = 0;
403 try {
404 count = Integer.parseInt(countStr);
405 } catch (NumberFormatException ex) {
406 LOG.error(ex);
408 return count;
411 private void processCustomStatus(final ServiceMessage msg) {
412 final Map<String,String> attrs = msg.getAttributes();
413 final String msgType = attrs.get(ATTR_KEY_TEST_TYPE);
414 if (msgType != null) {
415 if (msgType.equals(ATTR_VAL_TEST_STARTED)) {
416 fireOnCustomProgressTestStarted();
417 } else if (msgType.equals(ATTR_VAL_TEST_FAILED)) {
418 fireOnCustomProgressTestFailed();
420 return;
422 final String testsCategory = attrs.get(ATTR_KEY_TESTS_CATEGORY);
423 if (testsCategory != null) {
424 final String countStr = msg.getAttributes().get(ATTR_KEY_TEST_COUNT);
425 fireOnCustomProgressTestsCategory(testsCategory, convertToInt(countStr));
427 //noinspection UnnecessaryReturnStatement
428 return;