assertion for deprecated API added
[fedora-idea.git] / platform / smRunner / src / com / intellij / execution / testframework / sm / runner / OutputToGeneralTestEventsConverter.java
blob8ca8d4c2d75ea303d3f7d0c5c72d873aaf594f9e
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;
15 import java.util.Map;
17 /**
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 {
32 private Key myKey;
33 private String myText;
35 private OutputChunk(Key key, String text) {
36 myKey = key;
37 myText = text;
40 public Key getKey() {
41 return myKey;
44 public String getText() {
45 return myText;
48 public void append(String text) {
49 myText += 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);
69 } else {
70 processConsistentText(text, outputType, false);
74 /**
75 * Flashes the rest of stdout text buffer after output has been stopped
77 public void flushBufferBeforeTerminating() {
78 flushStdOutputBuffer();
81 public void dispose() {
82 setProcessor(null);
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());
101 else {
102 lastChunk = chunk;
103 chunks.add(chunk);
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) {
118 return;
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) {
133 try {
134 final ServiceMessage serviceMessage = ServiceMessage.parse(text);
135 if (serviceMessage != null) {
136 serviceMessage.visit(myServiceMessageVisitor);
137 } else {
138 // Filters \n
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
146 return;
148 //fire current output
149 fireOnUncapturedOutput(text, outputType);
152 catch (ParseException e) {
153 LOG.error(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);
276 @Nullable
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) {
281 // try old API
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'.");
285 return oldLocation;
287 return null;
289 return 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) {
302 //TODO
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
309 int duration = 0;
311 if (!StringUtil.isEmptyOrSpaces(durationStr)) {
312 try {
313 duration = Integer.parseInt(durationStr);
314 } catch (NumberFormatException ex) {
315 LOG.error(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) {
342 //Do nothing
345 public void visitProgressMessage(@NotNull final ProgressMessage progressMessage) {
346 //Do nothing
349 public void visitProgressStart(@NotNull final ProgressStart progressStart) {
350 //Do nothing
353 public void visitProgressFinish(@NotNull final ProgressFinish progressFinish) {
354 //Do nothing
357 public void visitBuildStatus(@NotNull final BuildStatus buildStatus) {
358 //Do nothing
361 public void visitBuildNumber(@NotNull final BuildNumber buildNumber) {
362 //Do nothing
365 public void visitBuildStatisticValue(@NotNull final BuildStatisticValue buildStatsValue) {
366 //Do nothing
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);
376 } else {
377 //Do nothing
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) {
387 int count = 0;
388 try {
389 count = Integer.parseInt(countStr);
390 } catch (NumberFormatException ex) {
391 LOG.error(ex);
393 return count;
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();
405 return;
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
413 return;