/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/OutputToGeneralTestEventsConverter.java

https://bitbucket.org/nbargnesi/idea · Java · 503 lines · 349 code · 75 blank · 79 comment · 68 complexity · ad97dd1fb2759e4e21575ceb53e0d29b MD5 · raw file

  1. /*
  2. * Copyright 2000-2009 JetBrains s.r.o.
  3. *
  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
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  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.
  15. */
  16. package com.intellij.execution.testframework.sm.runner;
  17. import com.intellij.execution.process.ProcessOutputTypes;
  18. import com.intellij.execution.testframework.TestConsoleProperties;
  19. import com.intellij.execution.testframework.sm.runner.events.*;
  20. import com.intellij.openapi.diagnostic.Logger;
  21. import com.intellij.openapi.util.Key;
  22. import com.intellij.openapi.util.text.StringUtil;
  23. import jetbrains.buildServer.messages.serviceMessages.*;
  24. import org.jetbrains.annotations.NonNls;
  25. import org.jetbrains.annotations.NotNull;
  26. import org.jetbrains.annotations.Nullable;
  27. import java.text.ParseException;
  28. import java.util.Map;
  29. import static com.intellij.execution.testframework.sm.runner.GeneralToSMTRunnerEventsConvertor.getTFrameworkPrefix;
  30. /**
  31. * @author Roman Chernyatchik
  32. * <p/>
  33. * This implementation also supports messages splitted in parts by early flush.
  34. * Implementation assumes that buffer is being flushed on line end or by timer,
  35. * i.e. incomming text contains no more than one line's end marker ('\r', '\n', or "\r\n")
  36. * (e.g. process was run with IDEA program's runner)
  37. */
  38. public class OutputToGeneralTestEventsConverter implements ProcessOutputConsumer {
  39. private static final Logger LOG = Logger.getInstance(OutputToGeneralTestEventsConverter.class.getName());
  40. private GeneralTestEventsProcessor myProcessor;
  41. private final MyServiceMessageVisitor myServiceMessageVisitor;
  42. private final String myTestFrameworkName;
  43. private final OutputLineSplitter mySplitter;
  44. private boolean myPendingLineBreakFlag;
  45. public OutputToGeneralTestEventsConverter(@NotNull final String testFrameworkName,
  46. @NotNull final TestConsoleProperties consoleProperties) {
  47. myTestFrameworkName = testFrameworkName;
  48. myServiceMessageVisitor = new MyServiceMessageVisitor();
  49. mySplitter = new OutputLineSplitter(consoleProperties.isEditable()) {
  50. @Override
  51. protected void onLineAvailable(@NotNull String text, @NotNull Key outputType, boolean tcLikeFakeOutput) {
  52. processConsistentText(text, outputType, tcLikeFakeOutput);
  53. }
  54. };
  55. }
  56. public void setProcessor(@Nullable final GeneralTestEventsProcessor processor) {
  57. myProcessor = processor;
  58. }
  59. public void dispose() {
  60. setProcessor(null);
  61. }
  62. public void process(final String text, final Key outputType) {
  63. mySplitter.process(text, outputType);
  64. }
  65. /**
  66. * Flashes the rest of stdout text buffer after output has been stopped
  67. */
  68. public void flushBufferBeforeTerminating() {
  69. mySplitter.flush();
  70. if (myPendingLineBreakFlag) {
  71. fireOnUncapturedLineBreak();
  72. }
  73. }
  74. private void fireOnUncapturedLineBreak() {
  75. fireOnUncapturedOutput("\n", ProcessOutputTypes.STDOUT);
  76. }
  77. private void processConsistentText(final String text, final Key outputType, boolean tcLikeFakeOutput) {
  78. try {
  79. if (!processServiceMessages(text, outputType, myServiceMessageVisitor)) {
  80. if (myPendingLineBreakFlag) {
  81. // output type for line break isn't important
  82. // we may use any, e.g. current one
  83. fireOnUncapturedLineBreak();
  84. myPendingLineBreakFlag = false;
  85. }
  86. // Filters \n
  87. String outputToProcess = text;
  88. if (tcLikeFakeOutput && text.endsWith("\n")) {
  89. // ServiceMessages protocol requires that every message
  90. // should start with new line, so such behaviour may led to generating
  91. // some number of useless \n.
  92. //
  93. // IDEA process handler flush output by size or line break
  94. // So:
  95. // 1. "a\n\nb\n" -> ["a\n", "\n", "b\n"]
  96. // 2. "a\n##teamcity[..]\n" -> ["a\n", "#teamcity[..]\n"]
  97. // We need distinguish 1) and 2) cases, in 2) first linebreak is redundant and must be ignored
  98. // in 2) linebreak must be considered as output
  99. // output will be in TestOutput message
  100. // Lets set myPendingLineBreakFlag if we meet "\n" and then ignore it or apply depending on
  101. // next output chunk
  102. myPendingLineBreakFlag = true;
  103. outputToProcess = outputToProcess.substring(0, outputToProcess.length() - 1);
  104. }
  105. //fire current output
  106. fireOnUncapturedOutput(outputToProcess, outputType);
  107. }
  108. else {
  109. myPendingLineBreakFlag = false;
  110. }
  111. }
  112. catch (ParseException e) {
  113. LOG.error(getTFrameworkPrefix(myTestFrameworkName) + "Error parsing text: [" + text + "]", e);
  114. }
  115. }
  116. protected boolean processServiceMessages(final String text,
  117. final Key outputType,
  118. final ServiceMessageVisitor visitor) throws ParseException {
  119. // service message parser expects line like "##teamcity[ .... ]" without whitespaces in the end.
  120. final ServiceMessage message = ServiceMessage.parse(text.trim());
  121. if (message != null) {
  122. message.visit(visitor);
  123. }
  124. return message != null;
  125. }
  126. private void fireOnTestStarted(@NotNull TestStartedEvent testStartedEvent) {
  127. // local variable is used to prevent concurrent modification
  128. final GeneralTestEventsProcessor processor = myProcessor;
  129. if (processor != null) {
  130. processor.onTestStarted(testStartedEvent);
  131. }
  132. }
  133. private void fireOnTestFailure(@NotNull TestFailedEvent testFailedEvent) {
  134. assertNotNull(testFailedEvent.getLocalizedFailureMessage());
  135. // local variable is used to prevent concurrent modification
  136. final GeneralTestEventsProcessor processor = myProcessor;
  137. if (processor != null) {
  138. processor.onTestFailure(testFailedEvent);
  139. }
  140. }
  141. private void fireOnTestIgnored(@NotNull TestIgnoredEvent testIgnoredEvent) {
  142. // local variable is used to prevent concurrent modification
  143. final GeneralTestEventsProcessor processor = myProcessor;
  144. if (processor != null) {
  145. processor.onTestIgnored(testIgnoredEvent);
  146. }
  147. }
  148. private void fireOnTestFinished(@NotNull TestFinishedEvent testFinishedEvent) {
  149. // local variable is used to prevent concurrent modification
  150. final GeneralTestEventsProcessor processor = myProcessor;
  151. if (processor != null) {
  152. processor.onTestFinished(testFinishedEvent);
  153. }
  154. }
  155. private void fireOnCustomProgressTestsCategory(final String categoryName,
  156. int testsCount) {
  157. assertNotNull(categoryName);
  158. final GeneralTestEventsProcessor processor = myProcessor;
  159. if (processor != null) {
  160. final boolean disableCustomMode = StringUtil.isEmpty(categoryName);
  161. processor.onCustomProgressTestsCategory(disableCustomMode ? null : categoryName,
  162. disableCustomMode ? 0 : testsCount);
  163. }
  164. }
  165. private void fireOnCustomProgressTestStarted() {
  166. final GeneralTestEventsProcessor processor = myProcessor;
  167. if (processor != null) {
  168. processor.onCustomProgressTestStarted();
  169. }
  170. }
  171. private void fireOnCustomProgressTestFailed() {
  172. final GeneralTestEventsProcessor processor = myProcessor;
  173. if (processor != null) {
  174. processor.onCustomProgressTestFailed();
  175. }
  176. }
  177. private void fireOnTestFrameworkAttached() {
  178. final GeneralTestEventsProcessor processor = myProcessor;
  179. if (processor != null) {
  180. processor.onTestsReporterAttached();
  181. }
  182. }
  183. private void fireOnTestOutput(@NotNull TestOutputEvent testOutputEvent) {
  184. // local variable is used to prevent concurrent modification
  185. final GeneralTestEventsProcessor processor = myProcessor;
  186. if (processor != null) {
  187. processor.onTestOutput(testOutputEvent);
  188. }
  189. }
  190. private void fireOnUncapturedOutput(final String text, final Key outputType) {
  191. assertNotNull(text);
  192. if (StringUtil.isEmpty(text)) {
  193. return;
  194. }
  195. // local variable is used to prevent concurrent modification
  196. final GeneralTestEventsProcessor processor = myProcessor;
  197. if (processor != null) {
  198. processor.onUncapturedOutput(text, outputType);
  199. }
  200. }
  201. private void fireOnTestsCountInSuite(final int count) {
  202. // local variable is used to prevent concurrent modification
  203. final GeneralTestEventsProcessor processor = myProcessor;
  204. if (processor != null) {
  205. processor.onTestsCountInSuite(count);
  206. }
  207. }
  208. private void fireOnSuiteStarted(@NotNull TestSuiteStartedEvent suiteStartedEvent) {
  209. // local variable is used to prevent concurrent modification
  210. final GeneralTestEventsProcessor processor = myProcessor;
  211. if (processor != null) {
  212. processor.onSuiteStarted(suiteStartedEvent);
  213. }
  214. }
  215. private void fireOnSuiteFinished(@NotNull TestSuiteFinishedEvent suiteFinishedEvent) {
  216. // local variable is used to prevent concurrent modification
  217. final GeneralTestEventsProcessor processor = myProcessor;
  218. if (processor != null) {
  219. processor.onSuiteFinished(suiteFinishedEvent);
  220. }
  221. }
  222. protected void fireOnErrorMsg(final String localizedMessage,
  223. @Nullable final String stackTrace,
  224. boolean isCritical) {
  225. assertNotNull(localizedMessage);
  226. // local variable is used to prevent concurrent modification
  227. final GeneralTestEventsProcessor processor = myProcessor;
  228. if (processor != null) {
  229. processor.onError(localizedMessage, stackTrace, isCritical);
  230. }
  231. }
  232. private void assertNotNull(final String s) {
  233. if (s == null) {
  234. LOG.error(getTFrameworkPrefix(myTestFrameworkName) + " @NotNull value is expected.");
  235. }
  236. }
  237. private class MyServiceMessageVisitor extends DefaultServiceMessageVisitor {
  238. @NonNls public static final String KEY_TESTS_COUNT = "testCount";
  239. @NonNls private static final String ATTR_KEY_TEST_ERROR = "error";
  240. @NonNls private static final String ATTR_KEY_TEST_COUNT = "count";
  241. @NonNls private static final String ATTR_KEY_TEST_DURATION = "duration";
  242. @NonNls private static final String ATTR_KEY_LOCATION_URL = "locationHint";
  243. @NonNls private static final String ATTR_KEY_LOCATION_URL_OLD = "location";
  244. @NonNls private static final String ATTR_KEY_STACKTRACE_DETAILS = "details";
  245. @NonNls private static final String ATTR_KEY_DIAGNOSTIC = "diagnosticInfo";
  246. @NonNls private static final String MESSAGE = "message";
  247. @NonNls private static final String TEST_REPORTER_ATTACHED = "enteredTheMatrix";
  248. @NonNls private static final String ATTR_KEY_STATUS = "status";
  249. @NonNls private static final String ATTR_VALUE_STATUS_ERROR = "ERROR";
  250. @NonNls private static final String ATTR_VALUE_STATUS_WARNING = "WARNING";
  251. @NonNls private static final String ATTR_KEY_TEXT = "text";
  252. @NonNls private static final String ATTR_KEY_ERROR_DETAILS = "errorDetails";
  253. @NonNls public static final String CUSTOM_STATUS = "customProgressStatus";
  254. @NonNls private static final String ATTR_KEY_TEST_TYPE = "type";
  255. @NonNls private static final String ATTR_KEY_TESTS_CATEGORY = "testsCategory";
  256. @NonNls private static final String ATTR_VAL_TEST_STARTED = "testStarted";
  257. @NonNls private static final String ATTR_VAL_TEST_FAILED = "testFailed";
  258. public void visitTestSuiteStarted(@NotNull final TestSuiteStarted suiteStarted) {
  259. final String locationUrl = fetchTestLocation(suiteStarted);
  260. TestSuiteStartedEvent suiteStartedEvent = new TestSuiteStartedEvent(suiteStarted, locationUrl);
  261. fireOnSuiteStarted(suiteStartedEvent);
  262. }
  263. @Nullable
  264. private String fetchTestLocation(final TestSuiteStarted suiteStarted) {
  265. final Map<String, String> attrs = suiteStarted.getAttributes();
  266. final String location = attrs.get(ATTR_KEY_LOCATION_URL);
  267. if (location == null) {
  268. // try old API
  269. final String oldLocation = attrs.get(ATTR_KEY_LOCATION_URL_OLD);
  270. if (oldLocation != null) {
  271. LOG.error(getTFrameworkPrefix(myTestFrameworkName)
  272. +
  273. "Test Runner API was changed for TeamCity 5.0 compatibility. Please use 'locationHint' attribute instead of 'location'.");
  274. return oldLocation;
  275. }
  276. return null;
  277. }
  278. return location;
  279. }
  280. public void visitTestSuiteFinished(@NotNull final TestSuiteFinished suiteFinished) {
  281. TestSuiteFinishedEvent finishedEvent = new TestSuiteFinishedEvent(suiteFinished);
  282. fireOnSuiteFinished(finishedEvent);
  283. }
  284. public void visitTestStarted(@NotNull final TestStarted testStarted) {
  285. // TODO
  286. // final String locationUrl = testStarted.getLocationHint();
  287. final String locationUrl = testStarted.getAttributes().get(ATTR_KEY_LOCATION_URL);
  288. TestStartedEvent testStartedEvent = new TestStartedEvent(testStarted, locationUrl);
  289. fireOnTestStarted(testStartedEvent);
  290. }
  291. public void visitTestFinished(@NotNull final TestFinished testFinished) {
  292. //TODO
  293. //final Integer duration = testFinished.getTestDuration();
  294. //fireOnTestFinished(testFinished.getTestName(), duration != null ? duration.intValue() : 0);
  295. final String durationStr = testFinished.getAttributes().get(ATTR_KEY_TEST_DURATION);
  296. // Test duration in milliseconds
  297. int duration = 0;
  298. if (!StringUtil.isEmptyOrSpaces(durationStr)) {
  299. duration = convertToInt(durationStr, testFinished);
  300. }
  301. TestFinishedEvent testFinishedEvent = new TestFinishedEvent(testFinished, duration);
  302. fireOnTestFinished(testFinishedEvent);
  303. }
  304. public void visitTestIgnored(@NotNull final TestIgnored testIgnored) {
  305. final String stacktrace = testIgnored.getAttributes().get(ATTR_KEY_STACKTRACE_DETAILS);
  306. fireOnTestIgnored(new TestIgnoredEvent(testIgnored, stacktrace));
  307. }
  308. public void visitTestStdOut(@NotNull final TestStdOut testStdOut) {
  309. fireOnTestOutput(new TestOutputEvent(testStdOut, testStdOut.getStdOut(), true));
  310. }
  311. public void visitTestStdErr(@NotNull final TestStdErr testStdErr) {
  312. fireOnTestOutput(new TestOutputEvent(testStdErr.getTestName(), testStdErr.getStdErr(), false));
  313. }
  314. public void visitTestFailed(@NotNull final TestFailed testFailed) {
  315. final boolean testError = testFailed.getAttributes().get(ATTR_KEY_TEST_ERROR) != null;
  316. TestFailedEvent testFailedEvent = new TestFailedEvent(testFailed, testError);
  317. fireOnTestFailure(testFailedEvent);
  318. }
  319. public void visitPublishArtifacts(@NotNull final PublishArtifacts publishArtifacts) {
  320. //Do nothing
  321. }
  322. public void visitProgressMessage(@NotNull final ProgressMessage progressMessage) {
  323. //Do nothing
  324. }
  325. public void visitProgressStart(@NotNull final ProgressStart progressStart) {
  326. //Do nothing
  327. }
  328. public void visitProgressFinish(@NotNull final ProgressFinish progressFinish) {
  329. //Do nothing
  330. }
  331. public void visitBuildStatus(@NotNull final BuildStatus buildStatus) {
  332. //Do nothing
  333. }
  334. public void visitBuildNumber(@NotNull final BuildNumber buildNumber) {
  335. //Do nothing
  336. }
  337. public void visitBuildStatisticValue(@NotNull final BuildStatisticValue buildStatsValue) {
  338. //Do nothing
  339. }
  340. @Override
  341. public void visitMessageWithStatus(@NotNull Message msg) {
  342. final Map<String, String> msgAttrs = msg.getAttributes();
  343. final String text = msgAttrs.get(ATTR_KEY_TEXT);
  344. if (!StringUtil.isEmpty(text)) {
  345. // msg status
  346. final String status = msgAttrs.get(ATTR_KEY_STATUS);
  347. if (status.equals(ATTR_VALUE_STATUS_ERROR)) {
  348. // error msg
  349. final String stackTrace = msgAttrs.get(ATTR_KEY_ERROR_DETAILS);
  350. fireOnErrorMsg(text, stackTrace, true);
  351. }
  352. else if (status.equals(ATTR_VALUE_STATUS_WARNING)) {
  353. // warning msg
  354. // let's show warning via stderr
  355. final String stackTrace = msgAttrs.get(ATTR_KEY_ERROR_DETAILS);
  356. fireOnErrorMsg(text, stackTrace, false);
  357. }
  358. else {
  359. // some other text
  360. // we cannot pass output type here but it is a service message
  361. // let's think that is was stdout
  362. fireOnUncapturedOutput(text, ProcessOutputTypes.STDOUT);
  363. }
  364. }
  365. }
  366. public void visitServiceMessage(@NotNull final ServiceMessage msg) {
  367. final String name = msg.getMessageName();
  368. if (KEY_TESTS_COUNT.equals(name)) {
  369. processTestCountInSuite(msg);
  370. }
  371. else if (CUSTOM_STATUS.equals(name)) {
  372. processCustomStatus(msg);
  373. }
  374. else if (MESSAGE.equals(name)) {
  375. final Map<String, String> msgAttrs = msg.getAttributes();
  376. final String text = msgAttrs.get(ATTR_KEY_TEXT);
  377. if (!StringUtil.isEmpty(text)) {
  378. // some other text
  379. // we cannot pass output type here but it is a service message
  380. // let's think that is was stdout
  381. fireOnUncapturedOutput(text, ProcessOutputTypes.STDOUT);
  382. }
  383. }
  384. else if (TEST_REPORTER_ATTACHED.equals(name)) {
  385. fireOnTestFrameworkAttached();
  386. }
  387. else {
  388. GeneralToSMTRunnerEventsConvertor.logProblem(LOG, "Unexpected service message:" + name, myTestFrameworkName);
  389. }
  390. }
  391. private void processTestCountInSuite(final ServiceMessage msg) {
  392. final String countStr = msg.getAttributes().get(ATTR_KEY_TEST_COUNT);
  393. fireOnTestsCountInSuite(convertToInt(countStr, msg));
  394. }
  395. private int convertToInt(String countStr, final ServiceMessage msg) {
  396. int count = 0;
  397. try {
  398. count = Integer.parseInt(countStr);
  399. }
  400. catch (NumberFormatException ex) {
  401. final String diagnosticInfo = msg.getAttributes().get(ATTR_KEY_DIAGNOSTIC);
  402. LOG.error(getTFrameworkPrefix(myTestFrameworkName) + "Parse integer error." + (diagnosticInfo == null ? "" : " " + diagnosticInfo),
  403. ex);
  404. }
  405. return count;
  406. }
  407. private void processCustomStatus(final ServiceMessage msg) {
  408. final Map<String, String> attrs = msg.getAttributes();
  409. final String msgType = attrs.get(ATTR_KEY_TEST_TYPE);
  410. if (msgType != null) {
  411. if (msgType.equals(ATTR_VAL_TEST_STARTED)) {
  412. fireOnCustomProgressTestStarted();
  413. }
  414. else if (msgType.equals(ATTR_VAL_TEST_FAILED)) {
  415. fireOnCustomProgressTestFailed();
  416. }
  417. return;
  418. }
  419. final String testsCategory = attrs.get(ATTR_KEY_TESTS_CATEGORY);
  420. if (testsCategory != null) {
  421. final String countStr = msg.getAttributes().get(ATTR_KEY_TEST_COUNT);
  422. fireOnCustomProgressTestsCategory(testsCategory, convertToInt(countStr, msg));
  423. //noinspection UnnecessaryReturnStatement
  424. return;
  425. }
  426. }
  427. }
  428. }